2 * neatmailx, a small mailx clone
4 * Copyright (C) 2009-2013 Ali Gholami Rudi <ali at rudi dot ir>
6 * This program is released under the modified BSD license.
14 #include <sys/types.h>
23 #define LEN(a) (sizeof(a) / sizeof((a)[0]))
24 #define MAILBUF (1 << 16)
25 #define MAXLINE (1 << 8)
26 #define BUFSIZE (1 << 12)
28 static struct mbox
*mbox
;
29 static struct sort
*sort
;
31 static int sel
[MAXMAILS
];
35 static int read_line(char *dst
, int size
)
37 static char buf
[BUFSIZE
];
42 int cur_len
= MIN(len
- cur
, size
- nw
- 1);
43 char *nl
= memchr(buf
+ cur
, '\n', cur_len
);
44 int nr
= nl
? nl
- buf
- cur
+ 1 : cur_len
;
46 memcpy(dst
+ nw
, buf
+ cur
, nr
);
51 if (nl
|| nw
== size
- 1)
54 if ((len
= read(0, buf
, BUFSIZE
)) <= 0)
60 static int utf8len(int c
)
62 if (c
> 0 && c
<= 0x7f)
77 static char *till_eol(char *r
, int len
, char *s
, char *e
, int fill
)
80 while (i
< len
&& s
&& s
< e
&& *s
) {
81 l
= utf8len((unsigned char) *s
);
82 if (*s
== '\r' || *s
== '\n') { /* ignoring line breaks */
100 static int msg_num(char *num
)
103 if (!*num
|| !strcmp(".", num
))
107 if (!strcmp("$", num
))
110 n
= cur
- (*(num
+ 1) ? atoi(num
+ 1) : 1);
112 n
= cur
+ (*(num
+ 1) ? atoi(num
+ 1) : 1);
113 if (!strcmp(",", num
)) {
115 while (++i
< sort
->n
) {
116 int stat
= sort
->mails
[i
]->stat
;
117 if (!(stat
& STAT_READ
) && (stat
& STAT_NEW
)) {
123 if (n
< 0 || n
>= mbox
->n
)
128 static int stat_char(struct mail
*mail
)
130 if (mail
->stat
& STAT_NEW
)
131 return mail
->stat
& STAT_READ
? 'R' : 'N';
133 return mail
->stat
& STAT_READ
? ' ' : 'U';
137 static char *memstr(char *s
, int slen
, char *r
, int rlen
)
141 if (!strncmp(s
, r
, MIN(d
- s
, rlen
)))
143 s
= memchr(s
+ 1, r
[0], d
- s
- 1);
148 static int search(char *args
, int all
, int dir
)
150 static char hdr_name
[MAXLINE
]; /* previous search header */
151 static char hdr_val
[MAXLINE
]; /* previous search keyword */
152 static int stat
; /* previous search status */
153 char *beg
= strchr(args
, '(');
154 char *spc
= beg
? strchr(beg
, ' ') : NULL
;
155 char *end
= spc
? strchr(spc
, ')') : NULL
;
157 if (beg
&& (!end
|| !spc
))
160 int hdr_len
= spc
- beg
- 1;
161 put_mem(hdr_name
, beg
+ 1, hdr_len
);
162 hdr_name
[hdr_len
] = '\0';
163 while (isspace(*spc
))
165 put_mem(hdr_val
, spc
, end
- spc
);
166 hdr_val
[end
- spc
] = '\0';
167 stat
= isalpha(*(args
+ 1)) ? toupper(*(args
+ 1)) : 0;
169 for (i
= cur
+ dir
; i
>= 0 && i
< sort
->n
; i
+= dir
) {
170 if (!stat
|| stat_char(sort
->mails
[i
]) == stat
) {
171 char *hdr
= mail_hdr(sort
->mails
[i
], hdr_name
);
172 if (hdr
&& memstr(hdr
, hdr_len(hdr
),
173 hdr_val
, strlen(hdr_val
))) {
185 static int sel_msgs(char *args
, int all
)
191 return search(args
, all
, +1);
193 return search(args
, all
, -1);
194 args
= cut_word(num
, args
);
199 if (*num
&& (com
= strchr(num
+ 1, ','))) {
200 char beg_str
[MAXLINE
];
201 memcpy(beg_str
, num
, com
- num
);
202 beg_str
[com
- num
] = '\0';
203 beg
= msg_num(beg_str
);
204 end
= msg_num(com
+ 1);
205 } else if (!strcmp("%", num
)) {
212 if (beg
!= -1 && end
!= -1) {
217 for (i
= beg
; i
<= end
; i
++)
220 args
= cut_word(num
, args
);
227 static char mimes_buf
[MAXMIME
][MAILBUF
];
228 static struct mail mimes_mail
[MAXMIME
];
229 static struct mail
*mimes_main
[MAXMIME
];
230 static int mimes_cur
;
232 static void mimes_clear(void)
235 memset(mimes_main
, 0, sizeof(mimes_main
));
238 static void mimes_free(struct mail
*mail
)
243 static void mimes_add(struct mail
*mail
)
245 struct mail
*mime_mail
= &mimes_mail
[mimes_cur
];
247 if (mimes_main
[mimes_cur
])
248 mimes_main
[mimes_cur
]->data
= NULL
;
249 mimes_main
[mimes_cur
] = mail
;
250 len
= mime_decode(mimes_buf
[mimes_cur
], mail
->head
,
251 MIN(mail
->len
, MAILBUF
));
252 memset(mime_mail
, 0, sizeof(*mime_mail
));
253 mail_read(mime_mail
, mimes_buf
[mimes_cur
], mimes_buf
[mimes_cur
] + len
);
254 /* handle ^From_ inside msgs */
255 mime_mail
->body_len
+= len
- mime_mail
->len
;
256 mime_mail
->len
= len
;
257 mail
->data
= mime_mail
;
258 mimes_cur
= (mimes_cur
+ 1) % MAXMIME
;
261 static struct mail
*mimes_get(struct mail
*mail
)
263 return mail
->data
? mail
->data
: mail
;
266 static void cmd_mime(char *pre
, char *arg
)
269 if (!sel_msgs(pre
, 1))
271 for (i
= 0; i
< nsel
; i
++)
272 mimes_add(sort
->mails
[sel
[i
]]);
275 static void show_mail(char *args
, int filthdr
)
279 char *pg_args
[] = {PAGER
, NULL
};
281 if (!sel_msgs(args
, 0))
283 mail
= sort
->mails
[cur
];
284 mail
->stat
|= STAT_READ
;
285 mail
= mimes_get(mail
);
287 s
+= mail_head(mail
, buf
, sizeof(buf
), hdr_filt
, LEN(hdr_filt
));
288 s
= put_mem(s
, mail
->body
, MIN(sizeof(buf
) - (s
- buf
),
291 s
= put_mem(s
, mail
->head
, MIN(sizeof(buf
), mail
->len
));
293 exec_pipe(PAGER
, pg_args
, buf
, s
- buf
);
296 static void cmd_page(char *pre
, char *arg
)
301 static void cmd_cat(char *pre
, char *arg
)
306 static int isreply(char *s
)
308 return tolower(s
[0]) == 'r' && tolower(s
[1]) == 'e' && s
[2] == ':';
311 static char *put_hdr(struct mail
*mail
, char *name
, char *dst
, int wid
, int fill
)
313 char *hdr
= mail_hdr(mail
, name
);
315 hdr
= hdr
+ strlen(name
) + 1;
316 if (TRIM_RE
&& isreply(hdr
))
320 dst
= till_eol(dst
, wid
, hdr
, hdr
+ hdr_len(hdr
), fill
? wid
: 0);
322 while (fill
&& --wid
>= 0)
328 #define put_clr(s, c) ((COLORS) ? (put_str(s, c)) : (s))
330 static void cmd_head(char *pre
, char *arg
)
334 if (!sel_msgs(pre
, 0))
336 beg
= cur
/ NHEAD
* NHEAD
;
337 end
= MIN(beg
+ NHEAD
, mbox
->n
);
338 for (i
= beg
; i
< end
; i
++) {
339 struct mail
*mail
= sort
->mails
[i
];
344 *s
++ = i
== cur
? '>' : ' ';
345 s
= put_clr(s
, mail
->stat
& STAT_NEW
? "\33[1;31m" : "\33[33m");
346 *s
++ = mail
->stat
& STAT_DEL
? 'D' : ' ';
347 *s
++ = stat_char(mail
);
348 s
= put_clr(s
, "\33[1;32m");
349 s
= put_int(s
, i
, DIGITS
);
352 mail
= mimes_get(mail
);
353 s
= put_clr(s
, "\33[0;34m");
354 s
= put_hdr(mail
, "From:", s
, 16, 1);
357 col
= 3 + DIGITS
+ 2 + 16 + 2;
358 indent
= sort_level(sort
, i
) * 2;
359 if (!mail_hdr(sort
->mails
[i
], "in-reply-to"))
360 s
= put_clr(s
, "\33[1;36m");
362 s
= put_clr(s
, indent
? "\33[32m" : "\33[36m");
363 while (indent
-- && col
< WIDTH
) {
367 s
= put_hdr(mail
, "Subject:", s
, WIDTH
- col
, 0);
368 s
= put_clr(s
, "\33[m");
370 write(1, fmt
, s
- fmt
);
374 static void cmd_z(char *pre
, char *arg
)
378 page
= MIN(cur
+ NHEAD
, mbox
->n
) / NHEAD
;
380 page
= cur
/ NHEAD
- (*(pre
+ 1) ? atoi(pre
+ 1) : 1);
382 page
= cur
/ NHEAD
+ (*(pre
+ 1) ? atoi(pre
+ 1) : 1);
384 page
= mbox
->n
/ NHEAD
;
387 if (page
>= 0 && page
* NHEAD
< mbox
->n
) {
393 static void cmd_del(char *pre
, char *arg
)
396 if (!sel_msgs(pre
, 1))
398 for (i
= 0; i
< nsel
; i
++)
399 sort
->mails
[sel
[i
]]->stat
|= STAT_DEL
;
402 static void cmd_undel(char *pre
, char *arg
)
405 if (!sel_msgs(pre
, 1))
407 for (i
= 0; i
< nsel
; i
++) {
408 sort
->mails
[sel
[i
]]->stat
&= ~STAT_DEL
;
409 mimes_free(sort
->mails
[sel
[i
]]);
413 static void print(char *s
)
415 write(1, s
, strlen(s
));
418 static int is_mbox(char *filename
)
422 return S_ISREG(st
.st_mode
);
425 static char *filename(char *path
)
427 char *slash
= strrchr(path
, '/');
428 return slash
? slash
+ 1 : path
;
431 static void sum_mbox(void)
439 s
= put_str(s
, filename(mbox
->path
));
440 s
= put_str(s
, ": ");
441 s
= put_int(s
, mbox
->n
, DIGITS
);
442 s
= put_str(s
, " msgs ");
443 for (i
= 0; i
< mbox
->n
; i
++) {
444 if (!(mbox
->mails
[i
].stat
& STAT_READ
)) {
446 if (mbox
->mails
[i
].stat
& STAT_NEW
)
449 if (mbox
->mails
[i
].stat
& STAT_DEL
)
453 s
= put_int(s
, new, DIGITS
);
454 s
= put_str(s
, " new ");
457 s
= put_int(s
, unread
, DIGITS
);
458 s
= put_str(s
, " unread ");
461 s
= put_int(s
, del
, DIGITS
);
462 s
= put_str(s
, " del ");
464 s
= put_str(s
, "\n");
468 static void open_mbox(char *filename
)
470 mbox
= mbox_alloc(filename
);
471 sort
= sort_alloc(mbox
, THREADED
? SORT_THREAD
: 0);
476 static void close_mbox(void)
483 static void mbox_old(struct mbox
*mbox
)
486 for (i
= 0; i
< mbox
->n
; i
++) {
487 struct mail
*mail
= &mbox
->mails
[i
];
488 mail
->stat
= (mail
->stat
& ~STAT_NEW
) | STAT_OLD
;
492 static int has_mail(char *path
)
495 if (stat(path
, &st
) == -1)
497 return st
.st_mtime
> st
.st_atime
;
500 static int mbox_path(char *path
, char *addr
)
505 s
= put_str(s
, FOLDER
);
506 s
= put_str(s
, addr
+ 1);
509 for (i
= 0; i
< LEN(boxes
); i
++) {
510 if (has_mail(boxes
[i
])) {
511 s
= put_str(s
, boxes
[i
]);
516 if (!strcmp(".", addr
) && mbox
)
517 s
= put_str(s
, mbox
->path
);
518 if (s
== path
&& *addr
)
519 s
= put_str(s
, addr
);
523 static void warn_nomem(void)
525 print("no mem for new msgs\n");
528 static void cmd_fold(char *pre
, char *arg
)
531 char path
[MAXPATHLEN
];
532 if (mbox_path(path
, arg
) && is_mbox(path
)) {
534 if (mbox_write(mbox
) == -1) {
546 static void cmd_news(char *pre
, char *arg
)
551 for (i
= 0; i
< LEN(boxes
); i
++) {
552 if (has_mail(boxes
[i
])) {
554 s
= put_str(s
, "\t");
555 s
= put_str(s
, filename(boxes
[i
]));
556 s
= put_str(s
, "\n");
562 static void cmd_inc(char *pre
, char *arg
)
565 int new = mbox_inc(mbox
);
572 s
= put_int(s
, new, DIGITS
);
573 s
= put_str(s
, " new\n");
578 static void cmd_next(char *pre
, char *arg
)
581 if (cur
+ 1 < mbox
->n
) {
591 static void copy_mail(char *msgs
, char *dst
, int del
)
598 if (!sel_msgs(msgs
, 1))
600 mbox_path(path
, dst
);
601 fd
= open(path
, O_WRONLY
| O_APPEND
| O_CREAT
, S_IRUSR
| S_IWUSR
);
603 print("failed to open mbox for writing\n");
606 for (i
= 0; i
< nsel
; i
++) {
607 struct mail
*mail
= sort
->mails
[sel
[i
]];
608 mail_write(mail
, fd
);
610 mail
->stat
|= STAT_DEL
;
615 static void cmd_copy(char *pre
, char *arg
)
617 copy_mail(pre
, arg
, 0);
620 static void cmd_move(char *pre
, char *arg
)
622 copy_mail(pre
, arg
, 1);
625 static void compose(struct draft
*draft
)
627 char record
[MAXPATHLEN
] = "";
630 char *pg_args
[] = {PAGER
, NULL
};
632 mbox_path(record
, RECORD
);
634 strcpy(record
, mbox
->path
);
635 while (read_line(line
, sizeof(line
)) > 0) {
637 if (!strcmp("~e", cmd
))
638 draft_edit(draft
, EDITOR
);
639 if (!strcmp("~v", cmd
))
640 draft_edit(draft
, VISUAL
);
641 if (!strcmp("~.", cmd
)) {
643 draft_save(draft
, record
);
647 if (!strcmp("~p", cmd
))
648 exec_pipe(PAGER
, pg_args
, draft
->mail
, draft
->len
);
649 if (!strcmp("~q", cmd
) || !strcmp("~x", cmd
))
654 static void cmd_mail(char *pre
, char *arg
)
657 draft_init(&draft
, *arg
? &arg
: NULL
, *arg
? 1 : 0, NULL
);
661 static void cmd_reply(char *pre
, char *arg
)
664 if (!sel_msgs(pre
, 0))
666 draft_reply(&draft
, mimes_get(sort
->mails
[cur
]));
670 static void prompt(void)
675 static void cmd_quit(char *pre
, char *arg
)
678 if (mbox_write(mbox
) == -1)
684 static void cmd_exit(char *pre
, char *arg
)
689 static void cmd_clear(char *pre
, char *arg
)
691 print("\x1b[H\x1b[J");
696 void (*cmd
)(char *pre
, char *arg
);
700 {"header", cmd_head
},
703 {"folder", cmd_fold
},
711 {"reply", cmd_reply
},
716 {"undelete", cmd_undel
},
722 {"clear", cmd_clear
},
725 static void cmd_parse(char *line
, char *pre
, char *cmd
, char *arg
)
727 while (*line
&& isspace(*line
))
729 while (*line
&& !isalpha(*line
)) {
730 if ((line
[0] == '/' || line
[0] == '?') &&
731 (line
[1] == '(' || line
[2] == '('))
732 while (*line
&& *line
!= ')')
737 while (*line
&& isspace(*line
))
739 while (*line
&& isalpha(*line
))
742 while (*line
&& isspace(*line
))
744 while (*line
&& !isspace(*line
))
749 static void loop(void)
757 while (read_line(line
, sizeof(line
)) > 0) {
758 cmd_parse(line
, pre
, cmd
, arg
);
764 for (i
= 0; i
< LEN(cmds
); i
++)
765 if (!strncmp(cmds
[i
].name
, cmd
, len
)) {
766 cmds
[i
].cmd(pre
, arg
);
776 int main(int argc
, char *argv
[])
779 char *filename
= NULL
;
782 if (argv
[i
][0] != '-')
784 if (!strcmp("-f", argv
[i
]))
785 filename
= argv
[++i
];
786 if (!strcmp("-s", argv
[i
]))
790 char path
[MAXPATHLEN
];
791 if (mbox_path(path
, filename
) && is_mbox(path
)) {
798 draft_init(&draft
, argv
+ i
, argc
- i
, subj
);