mime: cast to unsigned char for array accesses
[mailx.git] / mailx.c
blob14610161b0b2c263b5cf21bea9531caa19d1b7a7
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 int l = 1;
63 if (~c & 0xc0)
64 return 1;
65 while (l < 6 && c & (0x40 >> l))
66 l++;
67 return l + 1;
70 static char *till_eol(char *r, int len, char *s, char *e)
72 int i = 0;
73 while (i < len && s && s < e && *s) {
74 int l = utf8len(*s);
75 memcpy(r, s, l);
76 /* ignoring line breaks */
77 if (*s != '\r' && *s != '\n') {
78 r += l;
79 i++;
81 s += l;
83 return r;
86 static int msg_num(char *num)
88 int n = -1;
89 if (!*num || !strcmp(".", num))
90 n = cur;
91 if (isdigit(*num))
92 n = atoi(num);
93 if (!strcmp("$", num))
94 n = mbox->n - 1;
95 if (*num == '-')
96 n = cur - (*(num + 1) ? atoi(num + 1) : 1);
97 if (*num == '+')
98 n = cur + (*(num + 1) ? atoi(num + 1) : 1);
99 if (!strcmp(",", num)) {
100 int i = cur;
101 while (++i < sort->n) {
102 int stat = sort->mails[i]->stat;
103 if (!(stat & STAT_READ) && (stat & STAT_NEW)) {
104 n = i;
105 break;
109 if (n < 0 || n >= mbox->n)
110 return -1;
111 return n;
114 static int stat_char(struct mail *mail)
116 if (mail->stat & STAT_NEW)
117 return mail->stat & STAT_READ ? 'R' : 'N';
118 else
119 return mail->stat & STAT_READ ? ' ' : 'U';
122 static int search(char *args, int all, int back)
124 static char hdr_name[MAXLINE];
125 static char hdr_val[MAXLINE];
126 static int stat;
127 char *beg = strchr(args, '(');
128 char *spc = beg ? strchr(beg, ' ') : NULL;
129 char *end = spc ? strchr(spc, ')') : NULL;
130 int i = -1;
131 if (beg && (!end || !spc))
132 return 0;
133 if (beg) {
134 int hdr_len = spc - beg - 1;
135 put_mem(hdr_name, beg + 1, hdr_len);
136 hdr_name[hdr_len] = '\0';
137 while (isspace(*spc))
138 spc++;
139 put_mem(hdr_val, spc, end - spc);
140 hdr_val[end - spc] = '\0';
141 stat = isalpha(*(args + 1)) ? toupper(*(args + 1)) : 0;
143 if (all)
144 back = 0;
145 else
146 i = back ? cur - 1 : cur + 1;
147 while (i >= 0 && i < sort->n) {
148 char *hdr = mail_hdr(sort->mails[i], hdr_name);
149 char *s = hdr ? hdr + strlen(hdr_name) : NULL;
150 char *d = hdr ? hdr + hdr_len(hdr) : NULL;
151 while (s && s < d) {
152 if (!strncmp(hdr_val, s, strlen(hdr_val)) &&
153 (!stat || stat_char(sort->mails[i]) == stat)) {
154 if (!all) {
155 cur = i;
156 return 1;
158 sel[nsel++] = i;
159 break;
161 s = memchr(s + 1, hdr_val[0], d - s - 1);
163 i = back ? i - 1 : i + 1;
165 return nsel;
168 static int sel_msgs(char *args, int all)
170 char num[MAXLINE];
171 int i;
172 nsel = 0;
173 if (*args == '/')
174 return search(args, all, 0);
175 if (*args == '?')
176 return search(args, all, 1);
177 args = cut_word(num, args);
178 while (1) {
179 char *com;
180 int beg = -1;
181 int end = -1;
182 if (*num && (com = strchr(num + 1, ','))) {
183 char beg_str[MAXLINE];
184 memcpy(beg_str, num, com - num);
185 beg_str[com - num] = '\0';
186 beg = msg_num(beg_str);
187 end = msg_num(com + 1);
188 } else if (!strcmp("%", num)) {
189 beg = 0;
190 end = sort->n - 1;
191 } else {
192 beg = msg_num(num);
193 end = beg;
195 if (beg != -1 && end != -1) {
196 if (!all) {
197 cur = beg;
198 return 1;
200 for (i = beg; i <= end; i++)
201 sel[nsel++] = i;
203 args = cut_word(num, args);
204 if (!*num)
205 break;
207 return nsel;
210 static char mimes_buf[MAXMIME][MAILBUF];
211 static struct mail mimes_mail[MAXMIME];
212 static struct mail *mimes_main[MAXMIME];
213 static int mimes_cur;
215 static void mimes_clear(void)
217 mimes_cur = 0;
218 memset(mimes_main, 0, sizeof(mimes_main));
221 static void mimes_free(struct mail *mail)
223 mail->data = NULL;
226 static void mimes_add(struct mail *mail)
228 struct mail *mime_mail = &mimes_mail[mimes_cur];
229 int len;
230 if (mimes_main[mimes_cur])
231 mimes_main[mimes_cur]->data = NULL;
232 mimes_main[mimes_cur] = mail;
233 len = mime_decode(mimes_buf[mimes_cur], mail->head,
234 MIN(mail->len, MAILBUF));
235 memset(mime_mail, 0, sizeof(*mime_mail));
236 mail_read(mime_mail, mimes_buf[mimes_cur]);
237 /* handle ^From_ inside msgs */
238 mime_mail->body_len += len - mime_mail->len;
239 mime_mail->len = len;
240 mail->data = mime_mail;
241 mimes_cur = (mimes_cur + 1) % MAXMIME;
244 static struct mail *mimes_get(struct mail *mail)
246 return mail->data ? mail->data : mail;
249 static void cmd_mime(char *pre, char *arg)
251 int i;
252 if (!sel_msgs(pre, 1))
253 return;
254 for (i = 0; i < nsel; i++)
255 mimes_add(sort->mails[sel[i]]);
258 static void show_mail(char *args, int filthdr)
260 struct mail *mail;
261 char buf[MAILBUF];
262 char *pg_args[] = {PAGER, NULL};
263 char *s = buf;
264 if (!sel_msgs(args, 0))
265 return;
266 mail = sort->mails[cur];
267 mail->stat |= STAT_READ;
268 mail = mimes_get(mail);
269 if (filthdr) {
270 s += mail_head(mail, buf, sizeof(buf),
271 hdr_filt, ARRAY_SIZE(hdr_filt));
272 s = put_mem(s, mail->body, MIN(sizeof(buf) - (s - buf),
273 mail->body_len));
274 } else {
275 s = put_mem(s, mail->head, MIN(sizeof(buf), mail->len));
277 exec_pipe(PAGER, pg_args, buf, s - buf);
280 static void cmd_page(char *pre, char *arg)
282 show_mail(pre, 1);
285 static void cmd_cat(char *pre, char *arg)
287 show_mail(pre, 0);
290 static char *put_hdr(struct mail *mail, char *name, char *dst, int wid, int fill)
292 char *hdr = mail_hdr(mail, name);
293 char *lim = dst + wid;
294 if (hdr) {
295 hdr = hdr + strlen(name) + 1;
296 if (TRIM_RE && tolower(hdr[0]) == 'r' && tolower(hdr[1]) == 'e' &&
297 tolower(hdr[2]) == ':')
298 hdr += 3;
299 while (*hdr == ' ')
300 hdr++;
301 dst = till_eol(dst, wid, hdr, hdr + hdr_len(hdr));
303 while (fill && dst < lim)
304 *dst++ = ' ';
305 return dst;
308 #define put_clr(s, c) ((COLORS) ? (put_str(s, c)) : (s))
310 static void cmd_head(char *pre, char *arg)
312 int beg, end;
313 int i;
314 if (!sel_msgs(pre, 0))
315 return;
316 beg = cur / NHEAD * NHEAD;
317 end = MIN(beg + NHEAD, mbox->n);
318 for (i = beg; i < end; i++) {
319 struct mail *mail = sort->mails[i];
320 char fmt[MAXLINE];
321 char *s = fmt;
322 int col;
323 int indent;
324 *s++ = i == cur ? '>' : ' ';
325 s = put_clr(s, mail->stat & STAT_NEW ? "\33[1;31m" : "\33[33m");
326 *s++ = mail->stat & STAT_DEL ? 'D' : ' ';
327 *s++ = stat_char(mail);
328 s = put_clr(s, "\33[1;32m");
329 s = put_int(s, i, DIGITS);
330 *s++ = ' ';
331 *s++ = ' ';
332 mail = mimes_get(mail);
333 s = put_clr(s, "\33[0;34m");
334 s = put_hdr(mail, "From:", s, 16, 1);
335 *s++ = ' ';
336 *s++ = ' ';
337 col = 3 + DIGITS + 2 + 16 + 2;
338 indent = sort_level(sort, i) * 2;
339 if (!mail_hdr(sort->mails[i], "in-reply-to"))
340 s = put_clr(s, "\33[1;36m");
341 else
342 s = put_clr(s, indent ? "\33[32m" : "\33[36m");
343 while (indent-- && col < WIDTH) {
344 col++;
345 *s++ = ' ';
347 s = put_hdr(mail, "Subject:", s, WIDTH - col, 0);
348 s = put_clr(s, "\33[m");
349 *s++ = '\n';
350 write(1, fmt, s - fmt);
354 static void cmd_z(char *pre, char *arg)
356 int page = -1;
357 if (!*pre)
358 page = MIN(cur + NHEAD, mbox->n) / NHEAD;
359 if (*pre == '-')
360 page = cur / NHEAD - (*(pre + 1) ? atoi(pre + 1) : 1);
361 if (*pre == '+')
362 page = cur / NHEAD + (*(pre + 1) ? atoi(pre + 1) : 1);
363 if (*pre == '$')
364 page = mbox->n / NHEAD;
365 if (isdigit(*pre))
366 page = atoi(pre);
367 if (page >= 0 && page * NHEAD < mbox->n) {
368 cur = page * NHEAD;
369 cmd_head("", "");
373 static void cmd_del(char *pre, char *arg)
375 int i;
376 if (!sel_msgs(pre, 1))
377 return;
378 for (i = 0; i < nsel; i++)
379 sort->mails[sel[i]]->stat |= STAT_DEL;
382 static void cmd_undel(char *pre, char *arg)
384 int i;
385 if (!sel_msgs(pre, 1))
386 return;
387 for (i = 0; i < nsel; i++) {
388 sort->mails[sel[i]]->stat &= ~STAT_DEL;
389 mimes_free(sort->mails[sel[i]]);
393 static void print(char *s)
395 write(1, s, strlen(s));
398 static int is_mbox(char *filename)
400 struct stat st;
401 stat(filename, &st);
402 return S_ISREG(st.st_mode);
405 static char *filename(char *path)
407 char *slash = strrchr(path, '/');
408 return slash ? slash + 1 : path;
411 static void sum_mbox(void)
413 char msg[MAXLINE];
414 char *s = msg;
415 int new = 0;
416 int unread = 0;
417 int del = 0;
418 int i;
419 s = put_str(s, filename(mbox->path));
420 s = put_str(s, ": ");
421 s = put_int(s, mbox->n, DIGITS);
422 s = put_str(s, " msgs ");
423 for (i = 0; i < mbox->n; i++) {
424 if (!(mbox->mails[i].stat & STAT_READ)) {
425 unread++;
426 if (mbox->mails[i].stat & STAT_NEW)
427 new++;
429 if (mbox->mails[i].stat & STAT_DEL)
430 del++;
432 if (new) {
433 s = put_int(s, new, DIGITS);
434 s = put_str(s, " new ");
436 if (unread) {
437 s = put_int(s, unread, DIGITS);
438 s = put_str(s, " unread ");
440 if (del) {
441 s = put_int(s, del, DIGITS);
442 s = put_str(s, " del ");
444 s = put_str(s, "\n");
445 print(msg);
448 static void open_mbox(char *filename)
450 mbox = mbox_alloc(filename);
451 sort = sort_alloc(mbox, THREADED ? SORT_THREAD : 0);
452 cur = 0;
453 sum_mbox();
456 static void close_mbox(void)
458 mimes_clear();
459 sort_free(sort);
460 mbox_free(mbox);
463 static void mbox_old(struct mbox *mbox)
465 int i;
466 for (i = 0; i < mbox->n; i++) {
467 struct mail *mail = &mbox->mails[i];
468 mail->stat = (mail->stat & ~STAT_NEW) | STAT_OLD;
472 static int has_mail(char *path)
474 struct stat st;
475 if (stat(path, &st) == -1)
476 return 0;
477 return st.st_mtime > st.st_atime;
480 static int mbox_path(char *path, char *addr)
482 char *s = path;
483 int i;
484 if (*addr == '+') {
485 s = put_str(s, FOLDER);
486 s = put_str(s, addr + 1);
488 if (*addr == ',') {
489 for (i = 0; i < ARRAY_SIZE(boxes); i++) {
490 if (has_mail(boxes[i])) {
491 s = put_str(s, boxes[i]);
492 break;
496 if (!strcmp(".", addr) && mbox)
497 s = put_str(s, mbox->path);
498 if (s == path && *addr)
499 s = put_str(s, addr);
500 return s - path;
503 static void warn_nomem(void)
505 print("no mem for new msgs\n");
508 static void cmd_fold(char *pre, char *arg)
510 if (*arg) {
511 char path[MAXPATHLEN];
512 if (mbox_path(path, arg) && is_mbox(path)) {
513 mbox_old(mbox);
514 if (mbox_write(mbox) == -1) {
515 warn_nomem();
516 return;
518 close_mbox();
519 open_mbox(path);
521 } else {
522 sum_mbox();
526 static void cmd_news(char *pre, char *arg)
528 char msg[MAXLINE];
529 char *s;
530 int i;
531 for (i = 0; i < ARRAY_SIZE(boxes); i++) {
532 if (has_mail(boxes[i])) {
533 s = msg;
534 s = put_str(s, "\t");
535 s = put_str(s, filename(boxes[i]));
536 s = put_str(s, "\n");
537 print(msg);
542 static void cmd_inc(char *pre, char *arg)
544 char msg[MAXLINE];
545 int new = mbox_inc(mbox);
546 char *s;
547 if (new == -1)
548 warn_nomem();
549 if (new > 0) {
550 sort_inc(sort);
551 s = msg;
552 s = put_int(s, new, DIGITS);
553 s = put_str(s, " new\n");
554 print(msg);
558 static void cmd_next(char *pre, char *arg)
560 if (!pre || !*pre) {
561 if (cur + 1 < mbox->n) {
562 cur++;
563 } else {
564 print("EOF\n");
565 return;
568 show_mail(pre, 1);
571 static void copy_mail(char *msgs, char *dst, int del)
573 char path[MAXLINE];
574 int i;
575 int fd;
576 if (!dst || !*dst)
577 return;
578 if (!sel_msgs(msgs, 1))
579 return;
580 mbox_path(path, dst);
581 fd = open(path, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
582 if (fd == -1) {
583 print("failed to open mbox for writing\n");
584 return;
586 for (i = 0; i < nsel; i++) {
587 struct mail *mail = sort->mails[sel[i]];
588 mail_write(mail, fd);
589 if (del)
590 mail->stat |= STAT_DEL;
592 close(fd);
595 static void cmd_copy(char *pre, char *arg)
597 copy_mail(pre, arg, 0);
600 static void cmd_move(char *pre, char *arg)
602 copy_mail(pre, arg, 1);
605 static void compose(struct draft *draft)
607 char record[MAXPATHLEN] = "";
608 char line[MAXLINE];
609 char cmd[MAXLINE];
610 char *pg_args[] = {PAGER, NULL};
611 if (RECORD)
612 mbox_path(record, RECORD);
613 else if (mbox)
614 strcpy(record, mbox->path);
615 while (read_line(line, sizeof(line)) > 0) {
616 cut_word(cmd, line);
617 if (!strcmp("~e", cmd))
618 draft_edit(draft, EDITOR);
619 if (!strcmp("~v", cmd))
620 draft_edit(draft, VISUAL);
621 if (!strcmp("~.", cmd)) {
622 if (*record)
623 draft_save(draft, record);
624 draft_send(draft);
625 break;
627 if (!strcmp("~p", cmd))
628 exec_pipe(PAGER, pg_args, draft->mail, draft->len);
629 if (!strcmp("~q", cmd) || !strcmp("~x", cmd))
630 break;
634 static void cmd_mail(char *pre, char *arg)
636 struct draft draft;
637 draft_init(&draft, *arg ? &arg : NULL, *arg ? 1 : 0, NULL);
638 compose(&draft);
641 static void cmd_reply(char *pre, char *arg)
643 struct draft draft;
644 if (!sel_msgs(pre, 0))
645 return;
646 draft_reply(&draft, mimes_get(sort->mails[cur]));
647 compose(&draft);
650 static void prompt(void)
652 write(1, "? ", 2);
655 static void cmd_quit(char *pre, char *arg)
657 mbox_old(mbox);
658 if (mbox_write(mbox) == -1)
659 warn_nomem();
660 else
661 quit = 1;
664 static void cmd_exit(char *pre, char *arg)
666 quit = 1;
669 static void cmd_clear(char *pre, char *arg)
671 print("\x1b[H\x1b[J");
674 static struct cmds {
675 char *name;
676 void (*cmd)(char *pre, char *arg);
677 } cmds[] = {
678 {"page", cmd_page},
679 {"next", cmd_next},
680 {"header", cmd_head},
681 {"ls", cmd_head},
682 {"file", cmd_fold},
683 {"folder", cmd_fold},
684 {"inc", cmd_inc},
685 {"z", cmd_z},
686 {"mail", cmd_mail},
687 {"copy", cmd_copy},
688 {"cp", cmd_copy},
689 {"move", cmd_move},
690 {"mv", cmd_move},
691 {"reply", cmd_reply},
692 {"cat", cmd_cat},
693 {"delete", cmd_del},
694 {"rm", cmd_del},
695 {"cd", cmd_fold},
696 {"undelete", cmd_undel},
697 {"quit", cmd_quit},
698 {"exit", cmd_exit},
699 {"xit", cmd_exit},
700 {"news", cmd_news},
701 {"mime", cmd_mime},
702 {"clear", cmd_clear},
705 static void cmd_parse(char *line, char *pre, char *cmd, char *arg)
707 while (*line && isspace(*line))
708 line++;
709 while (*line && !isalpha(*line)) {
710 if ((line[0] == '/' || line[0] == '?') &&
711 (line[1] == '(' || line[2] == '('))
712 while (*line && *line != ')')
713 *pre++ = *line++;
714 *pre++ = *line++;
716 *pre = '\0';
717 while (*line && isspace(*line))
718 line++;
719 while (*line && isalpha(*line))
720 *cmd++ = *line++;
721 *cmd = '\0';
722 while (*line && isspace(*line))
723 line++;
724 while (*line && !isspace(*line))
725 *arg++ = *line++;
726 *arg = '\0';
729 static void loop(void)
731 char line[MAXLINE];
732 char pre[MAXLINE];
733 char cmd[MAXLINE];
734 char arg[MAXLINE];
735 int len;
736 prompt();
737 while (read_line(line, sizeof(line)) > 0) {
738 cmd_parse(line, pre, cmd, arg);
739 len = strlen(cmd);
740 int i;
741 if (!len)
742 cmd_next(pre, arg);
743 else
744 for (i = 0; i < ARRAY_SIZE(cmds); i++)
745 if (!strncmp(cmds[i].name, cmd, len)) {
746 cmds[i].cmd(pre, arg);
747 break;
749 if (quit)
750 break;
751 else
752 prompt();
756 int main(int argc, char *argv[])
758 int i = 0;
759 char *filename = NULL;
760 char *subj = NULL;
761 while (++i < argc) {
762 if (argv[i][0] != '-')
763 break;
764 if (!strcmp("-f", argv[i]))
765 filename = argv[++i];
766 if (!strcmp("-s", argv[i]))
767 subj = argv[++i];
769 if (filename) {
770 char path[MAXPATHLEN];
771 if (mbox_path(path, filename) && is_mbox(path)) {
772 open_mbox(path);
773 loop();
774 close_mbox();
776 } else {
777 struct draft draft;
778 draft_init(&draft, argv + i, argc - i, subj);
779 compose(&draft);
781 return 0;