add reply cmd
[mailx.git] / mailx.c
blobba0f6fd547acb8da0cbb077fa3138499e6eb30a3
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 MIN(a, b) ((a) < (b) ? (a) : (b))
17 #define MAILBUF (1 << 16)
18 #define MAXLINE (1 << 7)
19 #define BUFSIZE (1 << 12)
21 static struct mbox *mbox;
22 static struct sort *sort;
23 static int cur;
24 static int sel[MAXMAILS];
25 static int nsel;
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 (!strcmp(",", num)) {
70 int i = cur;
71 while (++i < sort->n) {
72 if (!(sort->mails[i]->stat & STAT_READ)) {
73 n = i;
74 break;
78 if (n < 0 || n >= mbox->n)
79 return -1;
80 return n;
83 static int search(char *args, int all)
85 static char hdr_name[MAXLINE];
86 static char hdr_val[MAXLINE];
87 char *beg = strchr(args, '(');
88 char *spc = beg ? strchr(beg, ' ') : NULL;
89 char *end = spc ? strchr(spc, ')') : NULL;
90 int i;
91 if (beg && (!end || !spc)) {
92 return 0;
94 if (beg) {
95 int hdr_len = spc - beg - 1;
96 put_mem(hdr_name, beg + 1, hdr_len);
97 hdr_name[hdr_len] = '\0';
98 while (isspace(*spc))
99 spc++;
100 put_mem(hdr_val, spc, end - spc);
101 hdr_val[end - spc] = '\0';
103 for (i = all ? 0 : cur + 1; i < sort->n; i++) {
104 char *hdr = mail_hdr(sort->mails[i], hdr_name);
105 char *s = hdr ? hdr + strlen(hdr_name) : NULL;
106 char *d = hdr ? hdr + hdr_len(hdr) : NULL;
107 while (s && s < d) {
108 if (!strncmp(hdr_val, s, strlen(hdr_val))) {
109 if (!all) {
110 cur = i;
111 return 1;
113 sel[nsel++] = i;
114 break;
116 s = memchr(s + 1, hdr_val[0], d - s - 1);
119 return nsel;
122 static int sel_msgs(char *args, int all)
124 char num[MAXLINE];
125 int i;
126 nsel = 0;
127 while (isspace(*args))
128 args++;
129 if (*args == '/')
130 return search(args, all);
131 args = cut_word(num, args);
132 while (1) {
133 char *dash;
134 int beg = -1;
135 int end = -1;
136 if ((dash = strchr(num, '-'))) {
137 char beg_str[MAXLINE];
138 memcpy(beg_str, num, dash - num);
139 beg_str[dash - num] = '\0';
140 beg = msg_num(beg_str);
141 end = msg_num(dash + 1);
142 } else {
143 beg = msg_num(num);
144 end = beg;
146 if (beg != -1 && end != -1) {
147 if (!all) {
148 cur = beg;
149 return 1;
151 for (i = beg; i <= end; i++)
152 sel[nsel++] = i;
154 args = cut_word(num, args);
155 if (!*num)
156 break;
158 return nsel;
161 static void cmd_page(char *args, int filthdr)
163 struct mail *mail;
164 char buf[MAILBUF];
165 char *pg_args[] = {PAGER, NULL};
166 char *s = buf;
167 if (!sel_msgs(args, 0))
168 return;
169 mail = sort->mails[cur];
170 mail->stat |= STAT_READ;
171 if (filthdr) {
172 s += mail_head(mail, buf, sizeof(buf),
173 hdr_filt, ARRAY_SIZE(hdr_filt));
174 s = put_mem(s, mail->body, MIN(sizeof(buf) - (s - buf),
175 mail->body_len));
176 } else {
177 s = put_mem(s, mail->head, mail->len);
179 exec_pipe(PAGER, pg_args, buf, s - buf);
182 static char *cut_cmd(char *dst, char *s)
184 while (isalpha(*s)) {
185 *dst++ = *s++;
187 *dst = '\0';
188 return s;
191 static char *put_hdr(struct mail *mail, char *name, int w, char *s)
193 char *hdr = mail_hdr(mail, name);
194 return till_eol(s, w, hdr ? hdr + strlen(name) + 1 : NULL);
197 static void cmd_head(char *args)
199 int beg, end;
200 int i;
201 if (!sel_msgs(args, 0))
202 return;
203 beg = cur / NHEAD * NHEAD;
204 end = MIN(beg + NHEAD, mbox->n);
205 for (i = beg; i < end; i++) {
206 struct mail *mail = sort->mails[i];
207 char fmt[MAXLINE];
208 char *s = fmt;
209 *s++ = i == cur ? '>' : ' ';
210 *s++ = mail->stat & STAT_DEL ? 'D' : ' ';
211 if (mail->stat & STAT_READ)
212 *s++ = ' ';
213 else
214 *s++ = mail->stat & STAT_NEW ? 'N' : 'O';
215 s = put_int(s, i, DIGITS);
216 *s++ = ' ';
217 *s++ = ' ';
218 s = put_hdr(mail, "From:", 16, s);
219 *s++ = ' ';
220 *s++ = ' ';
221 s = sort_draw(sort, i, s, WIDTH - (s - fmt));
222 s = put_hdr(mail, "Subject:", WIDTH - (s - fmt), s);
223 *s++ = '\n';
224 write(STDOUT_FILENO, fmt, s - fmt);
228 static void cmd_z(char *args)
230 char num[MAXLINE];
231 int page = -1;
232 cut_word(num, args);
233 if (!*num)
234 page = MIN(cur + NHEAD, mbox->n) / NHEAD;
235 if (*num == '+' || *num == '-') {
236 int d = (*num + 1) ? (*num == '-' ? -1 : 1) : atoi(num);
237 page = cur / NHEAD + d;
239 if (*num == '$')
240 page = mbox->n / NHEAD;
241 if (isdigit(*num))
242 page = atoi(num);
243 if (page >= 0 && page * NHEAD < mbox->n) {
244 cur = page * NHEAD;
245 cmd_head("");
249 static void cmd_del(char *args)
251 int i;
252 if (!sel_msgs(args, 1))
253 return;
254 for (i = 0; i < nsel; i++)
255 sort->mails[sel[i]]->stat |= STAT_DEL;
258 static void cmd_undel(char *args)
260 int i;
261 if (!sel_msgs(args, 1))
262 return;
263 for (i = 0; i < nsel; i++)
264 sort->mails[sel[i]]->stat &= ~STAT_DEL;
267 static void print(char *s)
269 write(STDOUT_FILENO, s, strlen(s));
272 static void open_mbox(char *filename)
274 mbox = mbox_alloc(filename);
275 sort = sort_alloc(mbox, THREADED ? SORT_THREAD : 0);
276 cur = 0;
279 static void close_mbox(void)
281 sort_free(sort);
282 mbox_free(mbox);
285 static int is_mbox(char *filename)
287 struct stat st;
288 stat(filename, &st);
289 return S_ISREG(st.st_mode);
292 static void sum_mbox(void)
294 char msg[MAXLINE];
295 char *s = msg;
296 int new = 0;
297 int unread = 0;
298 int del = 0;
299 int i;
300 s = put_str(s, mbox->path);
301 s = put_str(s, ": ");
302 s = put_int(s, mbox->n, DIGITS);
303 s = put_str(s, " messages ");
304 for (i = 0; i < mbox->n; i++) {
305 if (!(mbox->mails[i].stat & STAT_READ)) {
306 unread++;
307 if (mbox->mails[i].stat & STAT_NEW)
308 new++;
310 if (mbox->mails[i].stat & STAT_DEL)
311 del++;
313 if (new) {
314 s = put_int(s, new, DIGITS);
315 s = put_str(s, " new ");
317 if (unread) {
318 s = put_int(s, unread, DIGITS);
319 s = put_str(s, " unread ");
321 if (del) {
322 s = put_int(s, del, DIGITS);
323 s = put_str(s, " deleted ");
325 s = put_str(s, "\n");
326 print(msg);
329 static int has_mail(char *path)
331 struct stat st;
332 if (stat(path, &st) == -1)
333 return 0;
334 return st.st_mtime > st.st_atime;
337 static int mbox_path(char *path, char *addr)
339 char *s = path;
340 int i;
341 if (*addr == '+') {
342 s = put_str(s, FOLDER);
343 s = put_str(s, addr + 1);
345 if (*addr == ',') {
346 for (i = 0; i < ARRAY_SIZE(boxes); i++) {
347 if (has_mail(boxes[i])) {
348 s = put_str(s, boxes[i]);
349 break;
353 if (!strcmp(".", addr) && mbox)
354 s = put_str(s, mbox->path);
355 if (s == path && *addr)
356 s = put_str(s, addr);
357 return s - path;
360 static void cmd_fold(char *args)
362 char filename[MAXLINE];
363 args = cut_word(filename, args);
364 if (*filename) {
365 char path[MAXPATHLEN];
366 if (mbox_path(path, filename) && is_mbox(path)) {
367 close_mbox();
368 open_mbox(path);
370 } else {
371 sum_mbox();
375 static void warn_nomem(void)
377 print("no mem for new msgs\n");
380 static void cmd_inc(char *args)
382 char msg[MAXLINE];
383 int new = mbox_inc(mbox);
384 char *s;
385 int i;
386 if (new == -1)
387 warn_nomem();
388 if (new > 0) {
389 sort_inc(sort);
390 s = put_int(msg, new, DIGITS);
391 s = put_str(s, " new\n");
392 print(msg);
394 for (i = 0; i < ARRAY_SIZE(boxes); i++) {
395 if (has_mail(boxes[i])) {
396 char *box = strrchr(boxes[i], '/');
397 s = msg;
398 box = box ? box + 1 : boxes[i];
399 s = put_str(s, "mbox: ");
400 s = put_str(s, box);
401 s = put_str(s, "\n");
402 print(msg);
407 static void cmd_next(char *args)
409 char num[MAXLINE];
410 cut_word(num, args);
411 if (!*num) {
412 if (cur + 1 < mbox->n) {
413 cur++;
414 } else {
415 print("EOF\n");
416 return;
419 cmd_page(args, 1);
422 static void cmd_copy(char *args, int del)
424 char path[MAXLINE];
425 char last[MAXLINE];
426 int i;
427 char *spc = strrchr(args, ' ');
428 int fd;
429 cut_word(last, spc);
430 *spc = '\0';
431 if (!sel_msgs(args, 1) || !*last)
432 return;
433 mbox_path(path, last);
434 fd = open(path, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
435 if (fd == -1) {
436 print("failed to open mbox for writing\n");
437 return;
439 for (i = 0; i < nsel; i++) {
440 struct mail *mail = sort->mails[sel[i]];
441 mail_write(mail, fd);
442 if (del)
443 mail->stat |= STAT_DEL;
445 close(fd);
448 static void compose(struct draft *draft)
450 char record[MAXPATHLEN] = "";
451 char line[MAXLINE];
452 char cmd[MAXLINE];
453 if (RECORD)
454 mbox_path(record, RECORD);
455 else if (mbox)
456 strcpy(record, mbox->path);
457 while (read_line(line, sizeof(line)) > 0) {
458 cut_word(cmd, line);
459 if (!strcmp("~e", cmd))
460 draft_edit(draft);
461 if (!strcmp("~.", cmd)) {
462 if (*record)
463 draft_save(draft, record);
464 draft_send(draft);
465 break;
467 if (!strcmp("~p", cmd))
468 write(STDOUT_FILENO, draft->mail, draft->len);
469 if (!strcmp("~q", cmd) || !strcmp("~x", cmd))
470 break;
474 static void cmd_mail(char *args)
476 struct draft draft;
477 while (isspace(*args))
478 args++;
479 draft_init(&draft, *args ? &args : NULL, *args ? 1 : 0, NULL);
480 compose(&draft);
483 static void cmd_reply(char *args)
485 struct draft draft;
486 if (!sel_msgs(args, 0))
487 return;
488 draft_reply(&draft, sort->mails[cur]);
489 compose(&draft);
492 static void prompt(void)
494 write(STDOUT_FILENO, "? ", 2);
497 static void loop(void)
499 char line[MAXLINE];
500 char cmd[MAXLINE];
501 prompt();
502 while (read_line(line, sizeof(line)) > 0) {
503 char *args = cut_cmd(cmd, line);
504 int len = strlen(cmd);
505 if (!len)
506 cmd_next(args);
507 else if (!strncmp("page", cmd, len))
508 cmd_page(args, 1);
509 else if (!strncmp("next", cmd, len))
510 cmd_next(args);
511 else if (!strncmp("header", cmd, len))
512 cmd_head(args);
513 else if (!strncmp("file", cmd, len) ||
514 !strncmp("folder", cmd, len))
515 cmd_fold(args);
516 else if (!strncmp("inc", cmd, len))
517 cmd_inc(args);
518 else if (!strncmp("z", cmd, len))
519 cmd_z(args);
520 else if (!strncmp("mail", cmd, len))
521 cmd_mail(args);
522 else if (!strncmp("copy", cmd, len) ||
523 !strncmp("cp", cmd, len))
524 cmd_copy(args, 0);
525 else if (!strncmp("move", cmd, len) ||
526 !strncmp("mv", cmd, len))
527 cmd_copy(args, 1);
528 else if (!strncmp("reply", cmd, len))
529 cmd_reply(args);
530 else if (!strncmp("Page", cmd, len))
531 cmd_page(args, 0);
532 else if (!strncmp("delete", cmd, len) ||
533 !strncmp("rm", cmd, len))
534 cmd_del(args);
535 else if (!strncmp("undelete", cmd, len))
536 cmd_undel(args);
537 else if (!strncmp("quit", cmd, len)) {
538 if (mbox_write(mbox) == -1)
539 warn_nomem();
540 else
541 return;
543 else if (!strncmp("xit", cmd, len) ||
544 !strncmp("exit", cmd, len))
545 return;
546 prompt();
550 int main(int argc, char *argv[])
552 int i = 0;
553 char *filename = NULL;
554 char *subj = NULL;
555 while (++i < argc) {
556 if (argv[i][0] != '-')
557 break;
558 if (!strcmp("-f", argv[i]))
559 filename = argv[++i];
560 if (!strcmp("-s", argv[i]))
561 subj = argv[++i];
563 if (filename) {
564 char path[MAXPATHLEN];
565 if (mbox_path(path, filename) && is_mbox(path)) {
566 open_mbox(path);
567 loop();
568 close_mbox();
570 } else {
571 struct draft draft;
572 draft_init(&draft, argv + i, argc - i, subj);
573 compose(&draft);
575 return 0;