add a newline when appending to an mbox
[mailx.git] / mailx.c
blob0e6ad6eb6dee936b7340a6cc1cdf598520126c75
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;
26 static int read_line(char *dst, int size)
28 static char buf[BUFSIZE];
29 static int cur;
30 static int len;
31 int nw = 0;
32 while (1) {
33 int cur_len = MIN(len - cur, size - nw - 1);
34 char *nl = memchr(buf + cur, '\n', cur_len);
35 int nr = nl ? nl - buf - cur + 1 : cur_len;
36 if (nr) {
37 memcpy(dst + nw, buf + cur, nr);
38 nw += nr;
39 cur += nr;
40 dst[nw] = '\0';
42 if (nl || nw == size - 1)
43 return nw;
44 cur = 0;
45 if ((len = read(STDIN_FILENO, buf, BUFSIZE)) <= 0)
46 return -1;
48 return nw;
51 static char *till_eol(char *r, int len, char *s)
53 char *d = r + len;
54 while (r < d && s && *s && *s != '\r' && *s != '\n')
55 *r++ = *s++;
56 return r;
59 static int msg_num(char *num)
61 int n = -1;
62 if (!*num || !strcmp(".", num))
63 n = cur;
64 if (isdigit(*num))
65 n = atoi(num);
66 if (!strcmp("$", num))
67 n = mbox->n - 1;
68 if (*num == '-')
69 n = cur - (*(num + 1) ? atoi(num + 1) : 1);
70 if (*num == '+')
71 n = cur + (*(num + 1) ? atoi(num + 1) : 1);
72 if (!strcmp(",", num)) {
73 int i = cur;
74 while (++i < sort->n) {
75 int stat = sort->mails[i]->stat;
76 if (!(stat & STAT_READ) && (stat & STAT_NEW)) {
77 n = i;
78 break;
82 if (n < 0 || n >= mbox->n)
83 return -1;
84 return n;
87 static int stat_char(struct mail *mail)
89 if (mail->stat & STAT_NEW)
90 return mail->stat & STAT_READ ? 'R' : 'N';
91 else
92 return mail->stat & STAT_READ ? ' ' : 'U';
95 static int search(char *args, int all)
97 static char hdr_name[MAXLINE];
98 static char hdr_val[MAXLINE];
99 static int stat;
100 char *beg = strchr(args, '(');
101 char *spc = beg ? strchr(beg, ' ') : NULL;
102 char *end = spc ? strchr(spc, ')') : NULL;
103 int i;
104 if (beg && (!end || !spc)) {
105 return 0;
107 if (beg) {
108 int hdr_len = spc - beg - 1;
109 put_mem(hdr_name, beg + 1, hdr_len);
110 hdr_name[hdr_len] = '\0';
111 while (isspace(*spc))
112 spc++;
113 put_mem(hdr_val, spc, end - spc);
114 hdr_val[end - spc] = '\0';
115 stat = isalpha(*(args + 1)) ? toupper(*(args + 1)) : 0;
117 for (i = all ? 0 : cur + 1; i < sort->n; i++) {
118 char *hdr = mail_hdr(sort->mails[i], hdr_name);
119 char *s = hdr ? hdr + strlen(hdr_name) : NULL;
120 char *d = hdr ? hdr + hdr_len(hdr) : NULL;
121 while (s && s < d) {
122 if (!strncmp(hdr_val, s, strlen(hdr_val)) &&
123 (!stat || stat_char(sort->mails[i]) == stat)) {
124 if (!all) {
125 cur = i;
126 return 1;
128 sel[nsel++] = i;
129 break;
131 s = memchr(s + 1, hdr_val[0], d - s - 1);
134 return nsel;
137 static int sel_msgs(char *args, int all)
139 char num[MAXLINE];
140 int i;
141 nsel = 0;
142 while (isspace(*args))
143 args++;
144 if (*args == '/')
145 return search(args, all);
146 args = cut_word(num, args);
147 while (1) {
148 char *dash;
149 int beg = -1;
150 int end = -1;
151 if (*num && (dash = strchr(num + 1, '-'))) {
152 char beg_str[MAXLINE];
153 memcpy(beg_str, num, dash - num);
154 beg_str[dash - num] = '\0';
155 beg = msg_num(beg_str);
156 end = msg_num(dash + 1);
157 } else {
158 beg = msg_num(num);
159 end = beg;
161 if (beg != -1 && end != -1) {
162 if (!all) {
163 cur = beg;
164 return 1;
166 for (i = beg; i <= end; i++)
167 sel[nsel++] = i;
169 args = cut_word(num, args);
170 if (!*num)
171 break;
173 return nsel;
176 static void cmd_page(char *args, int filthdr)
178 struct mail *mail;
179 char buf[MAILBUF];
180 char *pg_args[] = {PAGER, NULL};
181 char *s = buf;
182 if (!sel_msgs(args, 0))
183 return;
184 mail = sort->mails[cur];
185 mail->stat |= STAT_READ;
186 if (filthdr) {
187 s += mail_head(mail, buf, sizeof(buf),
188 hdr_filt, ARRAY_SIZE(hdr_filt));
189 s = put_mem(s, mail->body, MIN(sizeof(buf) - (s - buf),
190 mail->body_len));
191 } else {
192 s = put_mem(s, mail->head, mail->len);
194 exec_pipe(PAGER, pg_args, buf, s - buf);
197 static char *cut_cmd(char *dst, char *s)
199 while (isalpha(*s)) {
200 *dst++ = *s++;
202 *dst = '\0';
203 return s;
206 static char *put_hdr(struct mail *mail, char *name, int w, char *s)
208 char *hdr = mail_hdr(mail, name);
209 return till_eol(s, w, hdr ? hdr + strlen(name) + 1 : NULL);
212 static void cmd_head(char *args)
214 int beg, end;
215 int i;
216 if (!sel_msgs(args, 0))
217 return;
218 beg = cur / NHEAD * NHEAD;
219 end = MIN(beg + NHEAD, mbox->n);
220 for (i = beg; i < end; i++) {
221 struct mail *mail = sort->mails[i];
222 char fmt[MAXLINE];
223 char *s = fmt;
224 *s++ = i == cur ? '>' : ' ';
225 *s++ = mail->stat & STAT_DEL ? 'D' : ' ';
226 *s++ = stat_char(mail);
227 s = put_int(s, i, DIGITS);
228 *s++ = ' ';
229 *s++ = ' ';
230 s = put_hdr(mail, "From:", 16, s);
231 *s++ = ' ';
232 *s++ = ' ';
233 s = sort_draw(sort, i, s, WIDTH - (s - fmt));
234 s = put_hdr(mail, "Subject:", WIDTH - (s - fmt), s);
235 *s++ = '\n';
236 write(STDOUT_FILENO, fmt, s - fmt);
240 static void cmd_z(char *args)
242 char num[MAXLINE];
243 int page = -1;
244 cut_word(num, args);
245 if (!*num)
246 page = MIN(cur + NHEAD, mbox->n) / NHEAD;
247 if (*num == '-')
248 page = cur / NHEAD - (*(num + 1) ? atoi(num + 1) : 1);
249 if (*num == '+')
250 page = cur / NHEAD + (*(num + 1) ? atoi(num + 1) : 1);
251 if (*num == '$')
252 page = mbox->n / NHEAD;
253 if (isdigit(*num))
254 page = atoi(num);
255 if (page >= 0 && page * NHEAD < mbox->n) {
256 cur = page * NHEAD;
257 cmd_head("");
261 static void cmd_del(char *args)
263 int i;
264 if (!sel_msgs(args, 1))
265 return;
266 for (i = 0; i < nsel; i++)
267 sort->mails[sel[i]]->stat |= STAT_DEL;
270 static void cmd_undel(char *args)
272 int i;
273 if (!sel_msgs(args, 1))
274 return;
275 for (i = 0; i < nsel; i++)
276 sort->mails[sel[i]]->stat &= ~STAT_DEL;
279 static void print(char *s)
281 write(STDOUT_FILENO, s, strlen(s));
284 static int is_mbox(char *filename)
286 struct stat st;
287 stat(filename, &st);
288 return S_ISREG(st.st_mode);
291 static void sum_mbox(void)
293 char msg[MAXLINE];
294 char *s = msg;
295 int new = 0;
296 int unread = 0;
297 int del = 0;
298 int i;
299 s = put_str(s, mbox->path);
300 s = put_str(s, ": ");
301 s = put_int(s, mbox->n, DIGITS);
302 s = put_str(s, " messages ");
303 for (i = 0; i < mbox->n; i++) {
304 if (!(mbox->mails[i].stat & STAT_READ)) {
305 unread++;
306 if (mbox->mails[i].stat & STAT_NEW)
307 new++;
309 if (mbox->mails[i].stat & STAT_DEL)
310 del++;
312 if (new) {
313 s = put_int(s, new, DIGITS);
314 s = put_str(s, " new ");
316 if (unread) {
317 s = put_int(s, unread, DIGITS);
318 s = put_str(s, " unread ");
320 if (del) {
321 s = put_int(s, del, DIGITS);
322 s = put_str(s, " deleted ");
324 s = put_str(s, "\n");
325 print(msg);
328 static void open_mbox(char *filename)
330 mbox = mbox_alloc(filename);
331 sort = sort_alloc(mbox, THREADED ? SORT_THREAD : 0);
332 cur = 0;
333 sum_mbox();
336 static void close_mbox(void)
338 sort_free(sort);
339 mbox_free(mbox);
342 static int has_mail(char *path)
344 struct stat st;
345 if (stat(path, &st) == -1)
346 return 0;
347 return st.st_mtime > st.st_atime;
350 static int mbox_path(char *path, char *addr)
352 char *s = path;
353 int i;
354 if (*addr == '+') {
355 s = put_str(s, FOLDER);
356 s = put_str(s, addr + 1);
358 if (*addr == ',') {
359 for (i = 0; i < ARRAY_SIZE(boxes); i++) {
360 if (has_mail(boxes[i])) {
361 s = put_str(s, boxes[i]);
362 break;
366 if (!strcmp(".", addr) && mbox)
367 s = put_str(s, mbox->path);
368 if (s == path && *addr)
369 s = put_str(s, addr);
370 return s - path;
373 static void warn_nomem(void)
375 print("no mem for new msgs\n");
378 static void cmd_fold(char *args)
380 char filename[MAXLINE];
381 args = cut_word(filename, args);
382 if (*filename) {
383 char path[MAXPATHLEN];
384 if (mbox_path(path, filename) && is_mbox(path)) {
385 if (mbox_write(mbox) == -1) {
386 warn_nomem();
387 return;
389 close_mbox();
390 open_mbox(path);
392 } else {
393 sum_mbox();
397 static void cmd_inc(char *args)
399 char msg[MAXLINE];
400 int new = mbox_inc(mbox);
401 char *s;
402 int i;
403 if (new == -1)
404 warn_nomem();
405 if (new > 0) {
406 sort_inc(sort);
407 s = put_int(msg, new, DIGITS);
408 s = put_str(s, " new\n");
409 print(msg);
411 for (i = 0; i < ARRAY_SIZE(boxes); i++) {
412 if (has_mail(boxes[i])) {
413 char *box = strrchr(boxes[i], '/');
414 s = msg;
415 box = box ? box + 1 : boxes[i];
416 s = put_str(s, "mbox: ");
417 s = put_str(s, box);
418 s = put_str(s, "\n");
419 print(msg);
424 static void cmd_next(char *args)
426 char num[MAXLINE];
427 cut_word(num, args);
428 if (!*num) {
429 if (cur + 1 < mbox->n) {
430 cur++;
431 } else {
432 print("EOF\n");
433 return;
436 cmd_page(args, 1);
439 static void cmd_copy(char *args, int del)
441 char path[MAXLINE];
442 char last[MAXLINE];
443 int i;
444 char *spc = strrchr(args, ' ');
445 int fd;
446 cut_word(last, spc);
447 *spc = '\0';
448 if (!sel_msgs(args, 1) || !*last)
449 return;
450 mbox_path(path, last);
451 fd = open(path, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
452 if (fd == -1) {
453 print("failed to open mbox for writing\n");
454 return;
456 if (file_size(fd))
457 write(fd, "\n", 1);
458 for (i = 0; i < nsel; i++) {
459 struct mail *mail = sort->mails[sel[i]];
460 mail_write(mail, fd);
461 if (del)
462 mail->stat |= STAT_DEL;
464 close(fd);
467 static void compose(struct draft *draft)
469 char record[MAXPATHLEN] = "";
470 char line[MAXLINE];
471 char cmd[MAXLINE];
472 if (RECORD)
473 mbox_path(record, RECORD);
474 else if (mbox)
475 strcpy(record, mbox->path);
476 while (read_line(line, sizeof(line)) > 0) {
477 cut_word(cmd, line);
478 if (!strcmp("~e", cmd))
479 draft_edit(draft);
480 if (!strcmp("~.", cmd)) {
481 if (*record)
482 draft_save(draft, record);
483 draft_send(draft);
484 break;
486 if (!strcmp("~p", cmd))
487 write(STDOUT_FILENO, draft->mail, draft->len);
488 if (!strcmp("~q", cmd) || !strcmp("~x", cmd))
489 break;
493 static void cmd_mail(char *args)
495 struct draft draft;
496 while (isspace(*args))
497 args++;
498 draft_init(&draft, *args ? &args : NULL, *args ? 1 : 0, NULL);
499 compose(&draft);
502 static void cmd_reply(char *args)
504 struct draft draft;
505 if (!sel_msgs(args, 0))
506 return;
507 draft_reply(&draft, sort->mails[cur]);
508 compose(&draft);
511 static void prompt(void)
513 write(STDOUT_FILENO, "? ", 2);
516 static void loop(void)
518 char line[MAXLINE];
519 char cmd[MAXLINE];
520 prompt();
521 while (read_line(line, sizeof(line)) > 0) {
522 char *args = cut_cmd(cmd, line);
523 int len = strlen(cmd);
524 if (!len)
525 cmd_next(args);
526 else if (!strncmp("page", cmd, len))
527 cmd_page(args, 1);
528 else if (!strncmp("next", cmd, len))
529 cmd_next(args);
530 else if (!strncmp("header", cmd, len))
531 cmd_head(args);
532 else if (!strncmp("file", cmd, len) ||
533 !strncmp("folder", cmd, len))
534 cmd_fold(args);
535 else if (!strncmp("inc", cmd, len))
536 cmd_inc(args);
537 else if (!strncmp("z", cmd, len))
538 cmd_z(args);
539 else if (!strncmp("mail", cmd, len))
540 cmd_mail(args);
541 else if (!strncmp("copy", cmd, len) ||
542 !strncmp("cp", cmd, len))
543 cmd_copy(args, 0);
544 else if (!strncmp("move", cmd, len) ||
545 !strncmp("mv", cmd, len))
546 cmd_copy(args, 1);
547 else if (!strncmp("reply", cmd, len))
548 cmd_reply(args);
549 else if (!strncmp("Page", cmd, len))
550 cmd_page(args, 0);
551 else if (!strncmp("delete", cmd, len) ||
552 !strncmp("rm", cmd, len))
553 cmd_del(args);
554 else if (!strncmp("undelete", cmd, len))
555 cmd_undel(args);
556 else if (!strncmp("quit", cmd, len)) {
557 if (mbox_write(mbox) == -1)
558 warn_nomem();
559 else
560 return;
562 else if (!strncmp("xit", cmd, len) ||
563 !strncmp("exit", cmd, len))
564 return;
565 prompt();
569 int main(int argc, char *argv[])
571 int i = 0;
572 char *filename = NULL;
573 char *subj = NULL;
574 while (++i < argc) {
575 if (argv[i][0] != '-')
576 break;
577 if (!strcmp("-f", argv[i]))
578 filename = argv[++i];
579 if (!strcmp("-s", argv[i]))
580 subj = argv[++i];
582 if (filename) {
583 char path[MAXPATHLEN];
584 if (mbox_path(path, filename) && is_mbox(path)) {
585 open_mbox(path);
586 loop();
587 close_mbox();
589 } else {
590 struct draft draft;
591 draft_init(&draft, argv + i, argc - i, subj);
592 compose(&draft);
594 return 0;