mailx: extract news cmd from inc
[mailx.git] / mailx.c
blob9678057b0e6fbf25e8676a230e78c4cf0298f50e
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 {
159 beg = msg_num(num);
160 end = beg;
162 if (beg != -1 && end != -1) {
163 if (!all) {
164 cur = beg;
165 return 1;
167 for (i = beg; i <= end; i++)
168 sel[nsel++] = i;
170 args = cut_word(num, args);
171 if (!*num)
172 break;
174 return nsel;
177 static void show_mail(char *args, int filthdr)
179 struct mail *mail;
180 char buf[MAILBUF];
181 char *pg_args[] = {PAGER, NULL};
182 char *s = buf;
183 if (!sel_msgs(args, 0))
184 return;
185 mail = sort->mails[cur];
186 mail->stat |= STAT_READ;
187 if (filthdr) {
188 s += mail_head(mail, buf, sizeof(buf),
189 hdr_filt, ARRAY_SIZE(hdr_filt));
190 s = put_mem(s, mail->body, MIN(sizeof(buf) - (s - buf),
191 mail->body_len));
192 } else {
193 s = put_mem(s, mail->head, mail->len);
195 exec_pipe(PAGER, pg_args, buf, s - buf);
198 static void cmd_page(char *args)
200 show_mail(args, 1);
203 static void cmd_cat(char *args)
205 show_mail(args, 0);
208 static char *cut_cmd(char *dst, char *s)
210 while (isalpha(*s)) {
211 *dst++ = *s++;
213 *dst = '\0';
214 return s;
217 static char *put_hdr(struct mail *mail, char *name, int w, char *s)
219 char *hdr = mail_hdr(mail, name);
220 return till_eol(s, w, hdr ? hdr + strlen(name) + 1 : NULL);
223 static void cmd_head(char *args)
225 int beg, end;
226 int i;
227 if (!sel_msgs(args, 0))
228 return;
229 beg = cur / NHEAD * NHEAD;
230 end = MIN(beg + NHEAD, mbox->n);
231 for (i = beg; i < end; i++) {
232 struct mail *mail = sort->mails[i];
233 char fmt[MAXLINE];
234 char *s = fmt;
235 *s++ = i == cur ? '>' : ' ';
236 *s++ = mail->stat & STAT_DEL ? 'D' : ' ';
237 *s++ = stat_char(mail);
238 s = put_int(s, i, DIGITS);
239 *s++ = ' ';
240 *s++ = ' ';
241 s = put_hdr(mail, "From:", 16, s);
242 *s++ = ' ';
243 *s++ = ' ';
244 s = sort_draw(sort, i, s, WIDTH - (s - fmt));
245 s = put_hdr(mail, "Subject:", WIDTH - (s - fmt), s);
246 *s++ = '\n';
247 write(STDOUT_FILENO, fmt, s - fmt);
251 static void cmd_z(char *args)
253 char num[MAXLINE];
254 int page = -1;
255 cut_word(num, args);
256 if (!*num)
257 page = MIN(cur + NHEAD, mbox->n) / NHEAD;
258 if (*num == '-')
259 page = cur / NHEAD - (*(num + 1) ? atoi(num + 1) : 1);
260 if (*num == '+')
261 page = cur / NHEAD + (*(num + 1) ? atoi(num + 1) : 1);
262 if (*num == '$')
263 page = mbox->n / NHEAD;
264 if (isdigit(*num))
265 page = atoi(num);
266 if (page >= 0 && page * NHEAD < mbox->n) {
267 cur = page * NHEAD;
268 cmd_head("");
272 static void cmd_del(char *args)
274 int i;
275 if (!sel_msgs(args, 1))
276 return;
277 for (i = 0; i < nsel; i++)
278 sort->mails[sel[i]]->stat |= STAT_DEL;
281 static void cmd_undel(char *args)
283 int i;
284 if (!sel_msgs(args, 1))
285 return;
286 for (i = 0; i < nsel; i++)
287 sort->mails[sel[i]]->stat &= ~STAT_DEL;
290 static void print(char *s)
292 write(STDOUT_FILENO, s, strlen(s));
295 static int is_mbox(char *filename)
297 struct stat st;
298 stat(filename, &st);
299 return S_ISREG(st.st_mode);
302 static char *filename(char *path)
304 char *slash = strrchr(path, '/');
305 return slash ? slash + 1 : path;
308 static void sum_mbox(void)
310 char msg[MAXLINE];
311 char *s = msg;
312 int new = 0;
313 int unread = 0;
314 int del = 0;
315 int i;
316 s = put_str(s, filename(mbox->path));
317 s = put_str(s, ": ");
318 s = put_int(s, mbox->n, DIGITS);
319 s = put_str(s, " messages ");
320 for (i = 0; i < mbox->n; i++) {
321 if (!(mbox->mails[i].stat & STAT_READ)) {
322 unread++;
323 if (mbox->mails[i].stat & STAT_NEW)
324 new++;
326 if (mbox->mails[i].stat & STAT_DEL)
327 del++;
329 if (new) {
330 s = put_int(s, new, DIGITS);
331 s = put_str(s, " new ");
333 if (unread) {
334 s = put_int(s, unread, DIGITS);
335 s = put_str(s, " unread ");
337 if (del) {
338 s = put_int(s, del, DIGITS);
339 s = put_str(s, " deleted ");
341 s = put_str(s, "\n");
342 print(msg);
345 static void open_mbox(char *filename)
347 mbox = mbox_alloc(filename);
348 sort = sort_alloc(mbox, THREADED ? SORT_THREAD : 0);
349 cur = 0;
350 sum_mbox();
353 static void close_mbox(void)
355 sort_free(sort);
356 mbox_free(mbox);
359 static int has_mail(char *path)
361 struct stat st;
362 if (stat(path, &st) == -1)
363 return 0;
364 return st.st_mtime > st.st_atime;
367 static int mbox_path(char *path, char *addr)
369 char *s = path;
370 int i;
371 if (*addr == '+') {
372 s = put_str(s, FOLDER);
373 s = put_str(s, addr + 1);
375 if (*addr == ',') {
376 for (i = 0; i < ARRAY_SIZE(boxes); i++) {
377 if (has_mail(boxes[i])) {
378 s = put_str(s, boxes[i]);
379 break;
383 if (!strcmp(".", addr) && mbox)
384 s = put_str(s, mbox->path);
385 if (s == path && *addr)
386 s = put_str(s, addr);
387 return s - path;
390 static void warn_nomem(void)
392 print("no mem for new msgs\n");
395 static void cmd_fold(char *args)
397 char filename[MAXLINE];
398 args = cut_word(filename, args);
399 if (*filename) {
400 char path[MAXPATHLEN];
401 if (mbox_path(path, filename) && is_mbox(path)) {
402 if (mbox_write(mbox) == -1) {
403 warn_nomem();
404 return;
406 close_mbox();
407 open_mbox(path);
409 } else {
410 sum_mbox();
414 static void cmd_news(char *args)
416 char msg[MAXLINE];
417 char *s;
418 int i;
419 for (i = 0; i < ARRAY_SIZE(boxes); i++) {
420 if (has_mail(boxes[i])) {
421 s = msg;
422 s = put_str(s, "\t");
423 s = put_str(s, filename(boxes[i]));
424 s = put_str(s, "\n");
425 print(msg);
430 static void cmd_inc(char *args)
432 char msg[MAXLINE];
433 int new = mbox_inc(mbox);
434 char *s;
435 if (new == -1)
436 warn_nomem();
437 if (new > 0) {
438 sort_inc(sort);
439 s = msg;
440 s = put_int(s, new, DIGITS);
441 s = put_str(s, " new\n");
442 print(msg);
446 static void cmd_next(char *args)
448 char num[MAXLINE];
449 cut_word(num, args);
450 if (!*num) {
451 if (cur + 1 < mbox->n) {
452 cur++;
453 } else {
454 print("EOF\n");
455 return;
458 show_mail(args, 1);
461 static void copy_mail(char *args, int del)
463 char path[MAXLINE];
464 char last[MAXLINE];
465 int i;
466 char *spc = strrchr(args, ' ');
467 int fd;
468 cut_word(last, spc);
469 *spc = '\0';
470 if (!sel_msgs(args, 1) || !*last)
471 return;
472 mbox_path(path, last);
473 fd = open(path, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
474 if (fd == -1) {
475 print("failed to open mbox for writing\n");
476 return;
478 for (i = 0; i < nsel; i++) {
479 struct mail *mail = sort->mails[sel[i]];
480 mail_write(mail, fd);
481 if (del)
482 mail->stat |= STAT_DEL;
484 close(fd);
487 static void cmd_copy(char *args)
489 copy_mail(args, 0);
492 static void cmd_move(char *args)
494 copy_mail(args, 1);
497 static void compose(struct draft *draft)
499 char record[MAXPATHLEN] = "";
500 char line[MAXLINE];
501 char cmd[MAXLINE];
502 char *pg_args[] = {PAGER, NULL};
503 if (RECORD)
504 mbox_path(record, RECORD);
505 else if (mbox)
506 strcpy(record, mbox->path);
507 while (read_line(line, sizeof(line)) > 0) {
508 cut_word(cmd, line);
509 if (!strcmp("~e", cmd))
510 draft_edit(draft);
511 if (!strcmp("~.", cmd)) {
512 if (*record)
513 draft_save(draft, record);
514 draft_send(draft);
515 break;
517 if (!strcmp("~p", cmd))
518 exec_pipe(PAGER, pg_args, draft->mail, draft->len);
519 if (!strcmp("~q", cmd) || !strcmp("~x", cmd))
520 break;
524 static void cmd_mail(char *args)
526 struct draft draft;
527 while (isspace(*args))
528 args++;
529 draft_init(&draft, *args ? &args : NULL, *args ? 1 : 0, NULL);
530 compose(&draft);
533 static void cmd_reply(char *args)
535 struct draft draft;
536 if (!sel_msgs(args, 0))
537 return;
538 draft_reply(&draft, sort->mails[cur]);
539 compose(&draft);
542 static void prompt(void)
544 write(STDOUT_FILENO, "? ", 2);
547 static void cmd_quit(char *args)
549 if (mbox_write(mbox) == -1)
550 warn_nomem();
551 else
552 quit = 1;
555 static void cmd_exit(char *args)
557 quit = 1;
560 static struct cmds {
561 char *name;
562 void (*cmd)(char *args);
563 } cmds[] = {
564 {"page", cmd_page},
565 {"next", cmd_next},
566 {"header", cmd_head},
567 {"ls", cmd_head},
568 {"file", cmd_fold},
569 {"folder", cmd_fold},
570 {"inc", cmd_inc},
571 {"z", cmd_z},
572 {"mail", cmd_mail},
573 {"copy", cmd_copy},
574 {"cp", cmd_copy},
575 {"move", cmd_move},
576 {"mv", cmd_move},
577 {"reply", cmd_reply},
578 {"cat", cmd_cat},
579 {"delete", cmd_del},
580 {"rm", cmd_del},
581 {"undelete", cmd_undel},
582 {"quit", cmd_quit},
583 {"exit", cmd_exit},
584 {"xit", cmd_exit},
585 {"news", cmd_news}
588 static void loop(void)
590 char line[MAXLINE];
591 char cmd[MAXLINE];
592 prompt();
593 while (read_line(line, sizeof(line)) > 0) {
594 char *args = cut_cmd(cmd, line);
595 int len = strlen(cmd);
596 int i;
597 if (!len)
598 cmd_next(args);
599 else
600 for (i = 0; i < ARRAY_SIZE(cmds); i++)
601 if (!strncmp(cmds[i].name, cmd, len)) {
602 cmds[i].cmd(args);
603 break;
605 if (quit)
606 break;
607 else
608 prompt();
612 int main(int argc, char *argv[])
614 int i = 0;
615 char *filename = NULL;
616 char *subj = NULL;
617 while (++i < argc) {
618 if (argv[i][0] != '-')
619 break;
620 if (!strcmp("-f", argv[i]))
621 filename = argv[++i];
622 if (!strcmp("-s", argv[i]))
623 subj = argv[++i];
625 if (filename) {
626 char path[MAXPATHLEN];
627 if (mbox_path(path, filename) && is_mbox(path)) {
628 open_mbox(path);
629 loop();
630 close_mbox();
632 } else {
633 struct draft draft;
634 draft_init(&draft, argv + i, argc - i, subj);
635 compose(&draft);
637 return 0;