mbox: preserve mail flags
[mailx.git] / mailx.c
blobc1ef506a927778cf6ba2f4fbc228394778e20725
1 #include <ctype.h>
2 #include <fcntl.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <unistd.h>
6 #include <sys/stat.h>
7 #include <sys/types.h>
8 #include "config.h"
9 #include "mbox.h"
10 #include "send.h"
11 #include "sort.h"
12 #include "str.h"
13 #include "util.h"
15 #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
16 #define MAILBUF (1 << 16)
17 #define MAXLINE (1 << 7)
18 #define BUFSIZE (1 << 12)
20 static struct mbox *mbox;
21 static struct sort *sort;
22 static int cur;
23 static int sel[MAXMAILS];
24 static int nsel;
25 static int quit;
27 static int read_line(char *dst, int size)
29 static char buf[BUFSIZE];
30 static int cur;
31 static int len;
32 int nw = 0;
33 while (1) {
34 int cur_len = MIN(len - cur, size - nw - 1);
35 char *nl = memchr(buf + cur, '\n', cur_len);
36 int nr = nl ? nl - buf - cur + 1 : cur_len;
37 if (nr) {
38 memcpy(dst + nw, buf + cur, nr);
39 nw += nr;
40 cur += nr;
41 dst[nw] = '\0';
43 if (nl || nw == size - 1)
44 return nw;
45 cur = 0;
46 if ((len = read(STDIN_FILENO, buf, BUFSIZE)) <= 0)
47 return -1;
49 return nw;
52 static char *till_eol(char *r, int len, char *s)
54 char *d = r + len;
55 while (r < d && s && *s && *s != '\r' && *s != '\n')
56 *r++ = *s++;
57 return r;
60 static int msg_num(char *num)
62 int n = -1;
63 if (!*num || !strcmp(".", num))
64 n = cur;
65 if (isdigit(*num))
66 n = atoi(num);
67 if (!strcmp("$", num))
68 n = mbox->n - 1;
69 if (*num == '-')
70 n = cur - (*(num + 1) ? atoi(num + 1) : 1);
71 if (*num == '+')
72 n = cur + (*(num + 1) ? atoi(num + 1) : 1);
73 if (!strcmp(",", num)) {
74 int i = cur;
75 while (++i < sort->n) {
76 int stat = sort->mails[i]->stat;
77 if (!(stat & STAT_READ) && (stat & STAT_NEW)) {
78 n = i;
79 break;
83 if (n < 0 || n >= mbox->n)
84 return -1;
85 return n;
88 static int stat_char(struct mail *mail)
90 if (mail->stat & STAT_NEW)
91 return mail->stat & STAT_READ ? 'R' : 'N';
92 else
93 return mail->stat & STAT_READ ? ' ' : 'U';
96 static int search(char *args, int all)
98 static char hdr_name[MAXLINE];
99 static char hdr_val[MAXLINE];
100 static int stat;
101 char *beg = strchr(args, '(');
102 char *spc = beg ? strchr(beg, ' ') : NULL;
103 char *end = spc ? strchr(spc, ')') : NULL;
104 int i;
105 if (beg && (!end || !spc)) {
106 return 0;
108 if (beg) {
109 int hdr_len = spc - beg - 1;
110 put_mem(hdr_name, beg + 1, hdr_len);
111 hdr_name[hdr_len] = '\0';
112 while (isspace(*spc))
113 spc++;
114 put_mem(hdr_val, spc, end - spc);
115 hdr_val[end - spc] = '\0';
116 stat = isalpha(*(args + 1)) ? toupper(*(args + 1)) : 0;
118 for (i = all ? 0 : cur + 1; i < sort->n; i++) {
119 char *hdr = mail_hdr(sort->mails[i], hdr_name);
120 char *s = hdr ? hdr + strlen(hdr_name) : NULL;
121 char *d = hdr ? hdr + hdr_len(hdr) : NULL;
122 while (s && s < d) {
123 if (!strncmp(hdr_val, s, strlen(hdr_val)) &&
124 (!stat || stat_char(sort->mails[i]) == stat)) {
125 if (!all) {
126 cur = i;
127 return 1;
129 sel[nsel++] = i;
130 break;
132 s = memchr(s + 1, hdr_val[0], d - s - 1);
135 return nsel;
138 static int sel_msgs(char *args, int all)
140 char num[MAXLINE];
141 int i;
142 nsel = 0;
143 while (isspace(*args))
144 args++;
145 if (*args == '/')
146 return search(args, all);
147 args = cut_word(num, args);
148 while (1) {
149 char *dash;
150 int beg = -1;
151 int end = -1;
152 if (*num && (dash = strchr(num + 1, '-'))) {
153 char beg_str[MAXLINE];
154 memcpy(beg_str, num, dash - num);
155 beg_str[dash - num] = '\0';
156 beg = msg_num(beg_str);
157 end = msg_num(dash + 1);
158 } else if (!strcmp("*", num)) {
159 beg = 0;
160 end = sort->n - 1;
161 } else {
162 beg = msg_num(num);
163 end = beg;
165 if (beg != -1 && end != -1) {
166 if (!all) {
167 cur = beg;
168 return 1;
170 for (i = beg; i <= end; i++)
171 sel[nsel++] = i;
173 args = cut_word(num, args);
174 if (!*num)
175 break;
177 return nsel;
180 static void show_mail(char *args, int filthdr)
182 struct mail *mail;
183 char buf[MAILBUF];
184 char *pg_args[] = {PAGER, NULL};
185 char *s = buf;
186 if (!sel_msgs(args, 0))
187 return;
188 mail = sort->mails[cur];
189 mail->stat |= STAT_READ;
190 if (filthdr) {
191 s += mail_head(mail, buf, sizeof(buf),
192 hdr_filt, ARRAY_SIZE(hdr_filt));
193 s = put_mem(s, mail->body, MIN(sizeof(buf) - (s - buf),
194 mail->body_len));
195 } else {
196 s = put_mem(s, mail->head, mail->len);
198 exec_pipe(PAGER, pg_args, buf, s - buf);
201 static void cmd_page(char *args)
203 show_mail(args, 1);
206 static void cmd_cat(char *args)
208 show_mail(args, 0);
211 static char *cut_cmd(char *dst, char *s)
213 while (isalpha(*s)) {
214 *dst++ = *s++;
216 *dst = '\0';
217 return s;
220 static char *put_hdr(struct mail *mail, char *name, int w, char *s)
222 char *hdr = mail_hdr(mail, name);
223 return till_eol(s, w, hdr ? hdr + strlen(name) + 1 : NULL);
226 static void cmd_head(char *args)
228 int beg, end;
229 int i;
230 if (!sel_msgs(args, 0))
231 return;
232 beg = cur / NHEAD * NHEAD;
233 end = MIN(beg + NHEAD, mbox->n);
234 for (i = beg; i < end; i++) {
235 struct mail *mail = sort->mails[i];
236 char fmt[MAXLINE];
237 char *s = fmt;
238 *s++ = i == cur ? '>' : ' ';
239 *s++ = mail->stat & STAT_DEL ? 'D' : ' ';
240 *s++ = stat_char(mail);
241 s = put_int(s, i, DIGITS);
242 *s++ = ' ';
243 *s++ = ' ';
244 s = put_hdr(mail, "From:", 16, s);
245 *s++ = ' ';
246 *s++ = ' ';
247 s = sort_draw(sort, i, s, WIDTH - (s - fmt));
248 s = put_hdr(mail, "Subject:", WIDTH - (s - fmt), s);
249 *s++ = '\n';
250 write(STDOUT_FILENO, fmt, s - fmt);
254 static void cmd_z(char *args)
256 char num[MAXLINE];
257 int page = -1;
258 cut_word(num, args);
259 if (!*num)
260 page = MIN(cur + NHEAD, mbox->n) / NHEAD;
261 if (*num == '-')
262 page = cur / NHEAD - (*(num + 1) ? atoi(num + 1) : 1);
263 if (*num == '+')
264 page = cur / NHEAD + (*(num + 1) ? atoi(num + 1) : 1);
265 if (*num == '$')
266 page = mbox->n / NHEAD;
267 if (isdigit(*num))
268 page = atoi(num);
269 if (page >= 0 && page * NHEAD < mbox->n) {
270 cur = page * NHEAD;
271 cmd_head("");
275 static void cmd_del(char *args)
277 int i;
278 if (!sel_msgs(args, 1))
279 return;
280 for (i = 0; i < nsel; i++)
281 sort->mails[sel[i]]->stat |= STAT_DEL;
284 static void cmd_undel(char *args)
286 int i;
287 if (!sel_msgs(args, 1))
288 return;
289 for (i = 0; i < nsel; i++)
290 sort->mails[sel[i]]->stat &= ~STAT_DEL;
293 static void print(char *s)
295 write(STDOUT_FILENO, s, strlen(s));
298 static int is_mbox(char *filename)
300 struct stat st;
301 stat(filename, &st);
302 return S_ISREG(st.st_mode);
305 static char *filename(char *path)
307 char *slash = strrchr(path, '/');
308 return slash ? slash + 1 : path;
311 static void sum_mbox(void)
313 char msg[MAXLINE];
314 char *s = msg;
315 int new = 0;
316 int unread = 0;
317 int del = 0;
318 int i;
319 s = put_str(s, filename(mbox->path));
320 s = put_str(s, ": ");
321 s = put_int(s, mbox->n, DIGITS);
322 s = put_str(s, " msgs ");
323 for (i = 0; i < mbox->n; i++) {
324 if (!(mbox->mails[i].stat & STAT_READ)) {
325 unread++;
326 if (mbox->mails[i].stat & STAT_NEW)
327 new++;
329 if (mbox->mails[i].stat & STAT_DEL)
330 del++;
332 if (new) {
333 s = put_int(s, new, DIGITS);
334 s = put_str(s, " new ");
336 if (unread) {
337 s = put_int(s, unread, DIGITS);
338 s = put_str(s, " unread ");
340 if (del) {
341 s = put_int(s, del, DIGITS);
342 s = put_str(s, " del ");
344 s = put_str(s, "\n");
345 print(msg);
348 static void open_mbox(char *filename)
350 mbox = mbox_alloc(filename);
351 sort = sort_alloc(mbox, THREADED ? SORT_THREAD : 0);
352 cur = 0;
353 sum_mbox();
356 static void close_mbox(void)
358 sort_free(sort);
359 mbox_free(mbox);
362 static void mbox_old(struct mbox *mbox)
364 int i;
365 for (i = 0; i < mbox->n; i++) {
366 struct mail *mail = &mbox->mails[i];
367 mail->stat = (mail->stat & ~STAT_NEW) | STAT_OLD;
371 static int has_mail(char *path)
373 struct stat st;
374 if (stat(path, &st) == -1)
375 return 0;
376 return st.st_mtime > st.st_atime;
379 static int mbox_path(char *path, char *addr)
381 char *s = path;
382 int i;
383 if (*addr == '+') {
384 s = put_str(s, FOLDER);
385 s = put_str(s, addr + 1);
387 if (*addr == ',') {
388 for (i = 0; i < ARRAY_SIZE(boxes); i++) {
389 if (has_mail(boxes[i])) {
390 s = put_str(s, boxes[i]);
391 break;
395 if (!strcmp(".", addr) && mbox)
396 s = put_str(s, mbox->path);
397 if (s == path && *addr)
398 s = put_str(s, addr);
399 return s - path;
402 static void warn_nomem(void)
404 print("no mem for new msgs\n");
407 static void cmd_fold(char *args)
409 char filename[MAXLINE];
410 args = cut_word(filename, args);
411 if (*filename) {
412 char path[MAXPATHLEN];
413 if (mbox_path(path, filename) && is_mbox(path)) {
414 mbox_old(mbox);
415 if (mbox_write(mbox) == -1) {
416 warn_nomem();
417 return;
419 close_mbox();
420 open_mbox(path);
422 } else {
423 sum_mbox();
427 static void cmd_news(char *args)
429 char msg[MAXLINE];
430 char *s;
431 int i;
432 for (i = 0; i < ARRAY_SIZE(boxes); i++) {
433 if (has_mail(boxes[i])) {
434 s = msg;
435 s = put_str(s, "\t");
436 s = put_str(s, filename(boxes[i]));
437 s = put_str(s, "\n");
438 print(msg);
443 static void cmd_inc(char *args)
445 char msg[MAXLINE];
446 int new = mbox_inc(mbox);
447 char *s;
448 if (new == -1)
449 warn_nomem();
450 if (new > 0) {
451 sort_inc(sort);
452 s = msg;
453 s = put_int(s, new, DIGITS);
454 s = put_str(s, " new\n");
455 print(msg);
459 static void cmd_next(char *args)
461 char num[MAXLINE];
462 cut_word(num, args);
463 if (!*num) {
464 if (cur + 1 < mbox->n) {
465 cur++;
466 } else {
467 print("EOF\n");
468 return;
471 show_mail(args, 1);
474 static void copy_mail(char *args, int del)
476 char path[MAXLINE];
477 char last[MAXLINE];
478 int i;
479 char *spc = strrchr(args, ' ');
480 int fd;
481 cut_word(last, spc);
482 *spc = '\0';
483 if (!sel_msgs(args, 1) || !*last)
484 return;
485 mbox_path(path, last);
486 fd = open(path, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
487 if (fd == -1) {
488 print("failed to open mbox for writing\n");
489 return;
491 for (i = 0; i < nsel; i++) {
492 struct mail *mail = sort->mails[sel[i]];
493 mail_write(mail, fd);
494 if (del)
495 mail->stat |= STAT_DEL;
497 close(fd);
500 static void cmd_copy(char *args)
502 copy_mail(args, 0);
505 static void cmd_move(char *args)
507 copy_mail(args, 1);
510 static void compose(struct draft *draft)
512 char record[MAXPATHLEN] = "";
513 char line[MAXLINE];
514 char cmd[MAXLINE];
515 char *pg_args[] = {PAGER, NULL};
516 if (RECORD)
517 mbox_path(record, RECORD);
518 else if (mbox)
519 strcpy(record, mbox->path);
520 while (read_line(line, sizeof(line)) > 0) {
521 cut_word(cmd, line);
522 if (!strcmp("~e", cmd))
523 draft_edit(draft);
524 if (!strcmp("~.", cmd)) {
525 if (*record)
526 draft_save(draft, record);
527 draft_send(draft);
528 break;
530 if (!strcmp("~p", cmd))
531 exec_pipe(PAGER, pg_args, draft->mail, draft->len);
532 if (!strcmp("~q", cmd) || !strcmp("~x", cmd))
533 break;
537 static void cmd_mail(char *args)
539 struct draft draft;
540 while (isspace(*args))
541 args++;
542 draft_init(&draft, *args ? &args : NULL, *args ? 1 : 0, NULL);
543 compose(&draft);
546 static void cmd_reply(char *args)
548 struct draft draft;
549 if (!sel_msgs(args, 0))
550 return;
551 draft_reply(&draft, sort->mails[cur]);
552 compose(&draft);
555 static void prompt(void)
557 write(STDOUT_FILENO, "? ", 2);
560 static void cmd_quit(char *args)
562 mbox_old(mbox);
563 if (mbox_write(mbox) == -1)
564 warn_nomem();
565 else
566 quit = 1;
569 static void cmd_exit(char *args)
571 quit = 1;
574 static struct cmds {
575 char *name;
576 void (*cmd)(char *args);
577 } cmds[] = {
578 {"page", cmd_page},
579 {"next", cmd_next},
580 {"header", cmd_head},
581 {"ls", cmd_head},
582 {"file", cmd_fold},
583 {"folder", cmd_fold},
584 {"inc", cmd_inc},
585 {"z", cmd_z},
586 {"mail", cmd_mail},
587 {"copy", cmd_copy},
588 {"cp", cmd_copy},
589 {"move", cmd_move},
590 {"mv", cmd_move},
591 {"reply", cmd_reply},
592 {"cat", cmd_cat},
593 {"delete", cmd_del},
594 {"rm", cmd_del},
595 {"undelete", cmd_undel},
596 {"quit", cmd_quit},
597 {"exit", cmd_exit},
598 {"xit", cmd_exit},
599 {"news", cmd_news}
602 static void loop(void)
604 char line[MAXLINE];
605 char cmd[MAXLINE];
606 prompt();
607 while (read_line(line, sizeof(line)) > 0) {
608 char *args = cut_cmd(cmd, line);
609 int len = strlen(cmd);
610 int i;
611 if (!len)
612 cmd_next(args);
613 else
614 for (i = 0; i < ARRAY_SIZE(cmds); i++)
615 if (!strncmp(cmds[i].name, cmd, len)) {
616 cmds[i].cmd(args);
617 break;
619 if (quit)
620 break;
621 else
622 prompt();
626 int main(int argc, char *argv[])
628 int i = 0;
629 char *filename = NULL;
630 char *subj = NULL;
631 while (++i < argc) {
632 if (argv[i][0] != '-')
633 break;
634 if (!strcmp("-f", argv[i]))
635 filename = argv[++i];
636 if (!strcmp("-s", argv[i]))
637 subj = argv[++i];
639 if (filename) {
640 char path[MAXPATHLEN];
641 if (mbox_path(path, filename) && is_mbox(path)) {
642 open_mbox(path);
643 loop();
644 close_mbox();
646 } else {
647 struct draft draft;
648 draft_init(&draft, argv + i, argc - i, subj);
649 compose(&draft);
651 return 0;