2 * neatmailx, a small mailx clone
4 * Copyright (C) 2009-2014 Ali Gholami Rudi <ali at rudi dot ir>
6 * This program is released under the Modified BSD license.
16 #include <sys/types.h>
25 #define LEN(a) (sizeof(a) / sizeof((a)[0]))
26 #define MAILBUF (1 << 16)
27 #define MAXLINE (1 << 8)
28 #define BUFSIZE (1 << 12)
30 static struct mbox
*mbox
; /* current mbox */
31 static struct mail
*sorted
[MAXMAILS
]; /* sorted mails in mbox */
32 static int levels
[MAXMAILS
]; /* identation of mails in sorted[] */
33 static int mcount
; /* number of mails in sorted[] */
34 static int cur
; /* current mail */
35 static int sel
[MAXMAILS
]; /* list of selected mails */
36 static int nsel
; /* number of selected mails */
39 static int readcmd(char *dst
, int size
)
41 return fgets(dst
, size
, stdin
) == NULL
;
44 static int utf8len(int c
)
46 if (c
> 0 && c
<= 0x7f)
61 static char *till_eol(char *r
, int len
, char *s
, char *e
, int fill
)
64 while (i
< len
&& s
&& s
< e
&& *s
) {
65 l
= utf8len((unsigned char) *s
);
66 if (*s
== '\r' || *s
== '\n') { /* ignoring line breaks */
84 static int msg_num(char *num
)
87 if (!*num
|| !strcmp(".", num
))
91 if (!strcmp("$", num
))
94 n
= cur
- (*(num
+ 1) ? atoi(num
+ 1) : 1);
96 n
= cur
+ (*(num
+ 1) ? atoi(num
+ 1) : 1);
97 if (!strcmp(",", num
)) {
99 while (++i
< mcount
) {
100 int stat
= sorted
[i
]->stat
;
101 if (!(stat
& STAT_READ
) && (stat
& STAT_NEW
)) {
107 if (n
< 0 || n
>= mbox
->n
)
112 static int stat_char(struct mail
*mail
)
114 if (mail
->stat
& STAT_NEW
)
115 return mail
->stat
& STAT_READ
? 'R' : 'N';
117 return mail
->stat
& STAT_READ
? ' ' : 'U';
121 static char *memstr(char *s
, int slen
, char *r
, int rlen
)
125 if (!strncmp(s
, r
, MIN(d
- s
, rlen
)))
127 s
= memchr(s
+ 1, r
[0], d
- s
- 1);
132 static int search(char *args
, int all
, int dir
)
134 static char hdr_name
[MAXLINE
]; /* previous search header */
135 static char hdr_val
[MAXLINE
]; /* previous search keyword */
136 static int stat
; /* previous search status */
137 char *beg
= strchr(args
, '(');
138 char *spc
= beg
? strchr(beg
, ' ') : NULL
;
139 char *end
= spc
? strchr(spc
, ')') : NULL
;
141 if (beg
&& (!end
|| !spc
))
144 int hdr_len
= spc
- beg
- 1;
145 put_mem(hdr_name
, beg
+ 1, hdr_len
);
146 hdr_name
[hdr_len
] = '\0';
147 while (isspace(*spc
))
149 put_mem(hdr_val
, spc
, end
- spc
);
150 hdr_val
[end
- spc
] = '\0';
151 stat
= isalpha(*(args
+ 1)) ? toupper(*(args
+ 1)) : 0;
153 for (i
= cur
+ dir
; i
>= 0 && i
< mcount
; i
+= dir
) {
154 if (!stat
|| stat_char(sorted
[i
]) == stat
) {
155 char *hdr
= mail_hdr(sorted
[i
], hdr_name
);
156 if (hdr
&& memstr(hdr
, hdr_len(hdr
),
157 hdr_val
, strlen(hdr_val
))) {
169 static int sel_msgs(char *args
, int all
)
175 return search(args
, all
, +1);
177 return search(args
, all
, -1);
178 args
= cut_word(num
, args
);
183 if (*num
&& (com
= strchr(num
+ 1, ','))) {
184 char beg_str
[MAXLINE
];
185 memcpy(beg_str
, num
, com
- num
);
186 beg_str
[com
- num
] = '\0';
187 beg
= msg_num(beg_str
);
188 end
= msg_num(com
+ 1);
189 } else if (!strcmp("%", num
)) {
196 if (beg
!= -1 && end
!= -1) {
201 for (i
= beg
; i
<= end
; i
++)
204 args
= cut_word(num
, args
);
211 static char mimes_buf
[MAXMIME
][MAILBUF
];
212 static struct mail mimes_mail
[MAXMIME
];
213 static struct mail
*mimes_main
[MAXMIME
];
214 static int mimes_cur
;
216 static void mimes_clear(void)
219 memset(mimes_main
, 0, sizeof(mimes_main
));
222 static void mimes_free(struct mail
*mail
)
225 for (i
= 0; i
< MAXMIME
; i
++)
226 if (mimes_main
[i
] == mail
)
227 mimes_main
[i
] = NULL
;
230 static void mimes_dec(struct mail
*mail
)
232 struct mail
*mime_mail
= &mimes_mail
[mimes_cur
];
234 mimes_main
[mimes_cur
] = mail
;
235 len
= mime_decode(mimes_buf
[mimes_cur
], mail
->head
,
236 MIN(mail
->len
, MAILBUF
));
237 memset(mime_mail
, 0, sizeof(*mime_mail
));
238 mail_read(mime_mail
, mimes_buf
[mimes_cur
], mimes_buf
[mimes_cur
] + len
);
239 /* handle ^From_ inside msgs */
240 mime_mail
->body_len
+= len
- mime_mail
->len
;
241 mime_mail
->len
= len
;
242 mimes_cur
= (mimes_cur
+ 1) % MAXMIME
;
245 static struct mail
*mimes_get(struct mail
*mail
)
248 for (i
= 0; i
< MAXMIME
; i
++)
249 if (mimes_main
[i
] == mail
)
250 return &mimes_mail
[i
];
254 static void cmd_mime(char *pre
, char *arg
)
257 if (!sel_msgs(pre
, 1))
259 for (i
= 0; i
< nsel
; i
++)
260 mimes_dec(sorted
[sel
[i
]]);
263 static void show_mail(char *args
, int filthdr
)
267 char *pg_args
[] = {PAGER
, NULL
};
269 if (!sel_msgs(args
, 0))
272 mail
->stat
|= STAT_READ
;
273 mail
= mimes_get(mail
);
275 s
+= mail_head(mail
, buf
, sizeof(buf
), hdr_filt
, LEN(hdr_filt
));
276 s
= put_mem(s
, mail
->body
, MIN(sizeof(buf
) - (s
- buf
),
279 s
= put_mem(s
, mail
->head
, MIN(sizeof(buf
), mail
->len
));
281 exec_pipe(PAGER
, pg_args
, buf
, s
- buf
);
284 static void cmd_page(char *pre
, char *arg
)
289 static void cmd_cat(char *pre
, char *arg
)
294 static int isreply(char *s
)
296 return tolower(s
[0]) == 'r' && tolower(s
[1]) == 'e' && s
[2] == ':';
299 static char *put_hdr(struct mail
*mail
, char *name
, char *dst
, int wid
, int fill
)
301 char *hdr
= mail_hdr(mail
, name
);
303 hdr
= hdr
+ strlen(name
) + 1;
304 if (TRIM_RE
&& isreply(hdr
))
308 dst
= till_eol(dst
, wid
, hdr
, hdr
+ hdr_len(hdr
), fill
? wid
: 0);
310 while (fill
&& --wid
>= 0)
316 #define put_clr(s, c) ((COLORS) ? (put_str(s, c)) : (s))
318 static void cmd_head(char *pre
, char *arg
)
322 if (!sel_msgs(pre
, 0))
324 beg
= cur
/ NHEAD
* NHEAD
;
325 end
= MIN(beg
+ NHEAD
, mbox
->n
);
326 for (i
= beg
; i
< end
; i
++) {
327 struct mail
*mail
= sorted
[i
];
332 *s
++ = i
== cur
? '>' : ' ';
333 s
= put_clr(s
, mail
->stat
& STAT_NEW
? "\33[1;31m" : "\33[33m");
334 *s
++ = mail
->stat
& STAT_DEL
? 'D' : ' ';
335 *s
++ = stat_char(mail
);
336 s
= put_clr(s
, "\33[1;32m");
337 s
= put_int(s
, i
, DIGITS
);
340 mail
= mimes_get(mail
);
341 s
= put_clr(s
, "\33[0;34m");
342 s
= put_hdr(mail
, "From:", s
, 16, 1);
345 col
= 3 + DIGITS
+ 2 + 16 + 2;
346 indent
= levels
[i
] * 2;
347 if (!mail_hdr(sorted
[i
], "in-reply-to"))
348 s
= put_clr(s
, "\33[1;36m");
350 s
= put_clr(s
, indent
? "\33[32m" : "\33[36m");
351 while (indent
-- && col
< WIDTH
) {
355 s
= put_hdr(mail
, "Subject:", s
, WIDTH
- col
, 0);
356 s
= put_clr(s
, "\33[m");
358 write(1, fmt
, s
- fmt
);
362 static void cmd_z(char *pre
, char *arg
)
366 page
= MIN(cur
+ NHEAD
, mbox
->n
) / NHEAD
;
368 page
= cur
/ NHEAD
- (*(pre
+ 1) ? atoi(pre
+ 1) : 1);
370 page
= cur
/ NHEAD
+ (*(pre
+ 1) ? atoi(pre
+ 1) : 1);
372 page
= mbox
->n
/ NHEAD
;
375 if (page
>= 0 && page
* NHEAD
< mbox
->n
) {
381 static void cmd_del(char *pre
, char *arg
)
384 if (!sel_msgs(pre
, 1))
386 for (i
= 0; i
< nsel
; i
++)
387 sorted
[sel
[i
]]->stat
|= STAT_DEL
;
390 static void cmd_undel(char *pre
, char *arg
)
393 if (!sel_msgs(pre
, 1))
395 for (i
= 0; i
< nsel
; i
++) {
396 sorted
[sel
[i
]]->stat
&= ~STAT_DEL
;
397 mimes_free(sorted
[sel
[i
]]);
401 static char *filename(char *path
)
403 char *slash
= strrchr(path
, '/');
404 return slash
? slash
+ 1 : path
;
407 static void mailx_sum(void)
409 int new = 0, unread
= 0, del
= 0;
411 printf("%s: %8d msgs ", filename(mbox
->path
), mbox
->n
);
412 for (i
= 0; i
< mbox
->n
; i
++) {
413 if (!(mbox
->mails
[i
].stat
& STAT_READ
)) {
415 if (mbox
->mails
[i
].stat
& STAT_NEW
)
418 if (mbox
->mails
[i
].stat
& STAT_DEL
)
422 printf(" %8d new", new);
424 printf(" %8d unread", unread
);
426 printf(" %8d del", del
);
430 static int mailx_isok(char *filename
)
434 return S_ISREG(st
.st_mode
);
437 static void mailx_sort(int from
)
440 for (i
= from
; i
< mbox
->n
; i
++)
441 sorted
[i
] = &mbox
->mails
[i
];
443 sort_mails(sorted
, levels
, mbox
->n
);
446 static void mailx_open(char *filename
)
448 mbox
= mbox_alloc(filename
);
450 printf("failed to open <%s>\n", filename
);
460 static void mailx_close(void)
466 static void mailx_old(struct mbox
*mbox
)
469 for (i
= 0; i
< mbox
->n
; i
++) {
470 struct mail
*mail
= &mbox
->mails
[i
];
471 mail
->stat
= (mail
->stat
& ~STAT_NEW
) | STAT_OLD
;
475 static int has_mail(char *path
)
478 if (stat(path
, &st
) == -1)
480 return st
.st_mtime
> st
.st_atime
;
483 static void mailx_path(char *path
, char *addr
)
486 if (!strcmp(".", addr
) && mbox
) {
487 strcpy(path
, mbox
->path
);
490 if (!strcmp(",", addr
)) {
491 for (i
= 0; i
< LEN(boxes
); i
++) {
492 if (has_mail(boxes
[i
])) {
493 strcpy(path
, boxes
[i
]);
499 sprintf(path
, "%s%s", FOLDER
, addr
+ 1);
504 static void warn_nomem(void)
506 printf("no mem for new msgs\n");
509 static void cmd_fold(char *pre
, char *arg
)
513 mailx_path(path
, arg
);
514 if (mailx_isok(path
)) {
516 if (mbox_write(mbox
) == -1) {
523 printf("cannot open <%s>\n", path
);
530 static void cmd_news(char *pre
, char *arg
)
533 for (i
= 0; i
< LEN(boxes
); i
++)
534 if (has_mail(boxes
[i
]))
535 printf("\t%s\n", filename(boxes
[i
]));
538 static void cmd_inc(char *pre
, char *arg
)
540 int new = mbox_inc(mbox
);
546 printf("%d new\n", new);
549 static void cmd_next(char *pre
, char *arg
)
552 if (cur
+ 1 < mbox
->n
) {
562 static void copy_mail(char *msgs
, char *dst
, int del
)
569 if (!sel_msgs(msgs
, 1))
571 mailx_path(path
, dst
);
572 fd
= open(path
, O_WRONLY
| O_APPEND
| O_CREAT
, S_IRUSR
| S_IWUSR
);
574 printf("failed to open <%s>\n", path
);
577 for (i
= 0; i
< nsel
; i
++) {
578 struct mail
*mail
= sorted
[sel
[i
]];
579 mail_write(mail
, fd
);
581 mail
->stat
|= STAT_DEL
;
586 static void cmd_copy(char *pre
, char *arg
)
588 copy_mail(pre
, arg
, 0);
591 static void cmd_move(char *pre
, char *arg
)
593 copy_mail(pre
, arg
, 1);
596 static void compose(struct draft
*draft
)
598 char record
[1024] = "";
601 char *pg_args
[] = {PAGER
, NULL
};
603 mailx_path(record
, RECORD
);
605 strcpy(record
, mbox
->path
);
606 while (!readcmd(line
, sizeof(line
))) {
608 if (!strcmp("~e", cmd
))
609 draft_edit(draft
, EDITOR
);
610 if (!strcmp("~v", cmd
))
611 draft_edit(draft
, VISUAL
);
612 if (!strcmp("~.", cmd
)) {
614 draft_save(draft
, record
);
618 if (!strcmp("~p", cmd
))
619 exec_pipe(PAGER
, pg_args
, draft
->mail
, draft
->len
);
620 if (!strcmp("~q", cmd
) || !strcmp("~x", cmd
))
625 static void cmd_mail(char *pre
, char *arg
)
628 draft_init(&draft
, *arg
? &arg
: NULL
, *arg
? 1 : 0, NULL
);
632 static void cmd_reply(char *pre
, char *arg
)
635 if (!sel_msgs(pre
, 0))
637 draft_reply(&draft
, mimes_get(sorted
[cur
]));
641 static void prompt(void)
647 static void cmd_quit(char *pre
, char *arg
)
650 if (mbox_write(mbox
) < 0)
656 static void cmd_exit(char *pre
, char *arg
)
661 static void cmd_clear(char *pre
, char *arg
)
663 printf("\x1b[H\x1b[J");
669 void (*cmd
)(char *pre
, char *arg
);
673 {"header", cmd_head
},
676 {"folder", cmd_fold
},
684 {"reply", cmd_reply
},
689 {"undelete", cmd_undel
},
695 {"clear", cmd_clear
},
698 static void cmd_parse(char *line
, char *pre
, char *cmd
, char *arg
)
700 while (*line
&& isspace(*line
))
702 while (*line
&& !isalpha(*line
)) {
703 if ((line
[0] == '/' || line
[0] == '?') &&
704 (line
[1] == '(' || line
[2] == '('))
705 while (*line
&& *line
!= ')')
710 while (*line
&& isspace(*line
))
712 while (*line
&& isalpha(*line
))
715 while (*line
&& isspace(*line
))
717 while (*line
&& !isspace(*line
))
722 static void loop(void)
731 while (!quit
&& !readcmd(line
, sizeof(line
))) {
732 cmd_parse(line
, pre
, cmd
, arg
);
737 for (i
= 0; i
< LEN(cmds
); i
++) {
738 if (!strncmp(cmds
[i
].name
, cmd
, len
)) {
739 cmds
[i
].cmd(pre
, arg
);
749 int main(int argc
, char *argv
[])
751 char *filename
= NULL
;
756 if (argv
[i
][0] != '-')
758 if (!strcmp("-f", argv
[i
]))
759 filename
= argv
[++i
];
760 if (!strcmp("-s", argv
[i
]))
763 signal(SIGPIPE
, SIG_IGN
);
765 mailx_path(path
, filename
);
766 if (mailx_isok(path
)) {
771 printf("cannot open <%s>\n", path
);
775 draft_init(&draft
, argv
+ i
, argc
- i
, subj
);