4 * Copyright (C) 2009-2010 Ali Gholami Rudi
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License, as published by the
8 * Free Software Foundation.
16 #include <sys/types.h>
25 #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
26 #define MAILBUF (1 << 16)
27 #define MAXLINE (1 << 7)
28 #define BUFSIZE (1 << 12)
30 static struct mbox
*mbox
;
31 static struct sort
*sort
;
33 static int sel
[MAXMAILS
];
37 static int read_line(char *dst
, int size
)
39 static char buf
[BUFSIZE
];
44 int cur_len
= MIN(len
- cur
, size
- nw
- 1);
45 char *nl
= memchr(buf
+ cur
, '\n', cur_len
);
46 int nr
= nl
? nl
- buf
- cur
+ 1 : cur_len
;
48 memcpy(dst
+ nw
, buf
+ cur
, nr
);
53 if (nl
|| nw
== size
- 1)
56 if ((len
= read(STDIN_FILENO
, buf
, BUFSIZE
)) <= 0)
62 static int utf8len(int c
)
67 while (l
< 6 && c
& (0x40 >> l
))
72 static char *till_eol(char *r
, int len
, char *s
, char *e
)
75 while (i
< len
&& s
&& s
< e
&& *s
) {
78 /* ignoring line breaks */
79 if (*s
!= '\r' && *s
!= '\n') {
88 static int msg_num(char *num
)
91 if (!*num
|| !strcmp(".", num
))
95 if (!strcmp("$", num
))
98 n
= cur
- (*(num
+ 1) ? atoi(num
+ 1) : 1);
100 n
= cur
+ (*(num
+ 1) ? atoi(num
+ 1) : 1);
101 if (!strcmp(",", num
)) {
103 while (++i
< sort
->n
) {
104 int stat
= sort
->mails
[i
]->stat
;
105 if (!(stat
& STAT_READ
) && (stat
& STAT_NEW
)) {
111 if (n
< 0 || n
>= mbox
->n
)
116 static int stat_char(struct mail
*mail
)
118 if (mail
->stat
& STAT_NEW
)
119 return mail
->stat
& STAT_READ
? 'R' : 'N';
121 return mail
->stat
& STAT_READ
? ' ' : 'U';
124 static int search(char *args
, int all
)
126 static char hdr_name
[MAXLINE
];
127 static char hdr_val
[MAXLINE
];
129 char *beg
= strchr(args
, '(');
130 char *spc
= beg
? strchr(beg
, ' ') : NULL
;
131 char *end
= spc
? strchr(spc
, ')') : NULL
;
133 if (beg
&& (!end
|| !spc
)) {
137 int hdr_len
= spc
- beg
- 1;
138 put_mem(hdr_name
, beg
+ 1, hdr_len
);
139 hdr_name
[hdr_len
] = '\0';
140 while (isspace(*spc
))
142 put_mem(hdr_val
, spc
, end
- spc
);
143 hdr_val
[end
- spc
] = '\0';
144 stat
= isalpha(*(args
+ 1)) ? toupper(*(args
+ 1)) : 0;
146 for (i
= all
? 0 : cur
+ 1; i
< sort
->n
; i
++) {
147 char *hdr
= mail_hdr(sort
->mails
[i
], hdr_name
);
148 char *s
= hdr
? hdr
+ strlen(hdr_name
) : NULL
;
149 char *d
= hdr
? hdr
+ hdr_len(hdr
) : NULL
;
151 if (!strncmp(hdr_val
, s
, strlen(hdr_val
)) &&
152 (!stat
|| stat_char(sort
->mails
[i
]) == stat
)) {
160 s
= memchr(s
+ 1, hdr_val
[0], d
- s
- 1);
166 static int sel_msgs(char *args
, int all
)
172 return search(args
, all
);
173 args
= cut_word(num
, args
);
178 if (*num
&& (com
= strchr(num
+ 1, ','))) {
179 char beg_str
[MAXLINE
];
180 memcpy(beg_str
, num
, com
- num
);
181 beg_str
[com
- num
] = '\0';
182 beg
= msg_num(beg_str
);
183 end
= msg_num(com
+ 1);
184 } else if (!strcmp("%", num
)) {
191 if (beg
!= -1 && end
!= -1) {
196 for (i
= beg
; i
<= end
; i
++)
199 args
= cut_word(num
, args
);
206 static char mimes_buf
[MAXMIME
][MAILBUF
];
207 static struct mail mimes_mail
[MAXMIME
];
208 static struct mail
*mimes_main
[MAXMIME
];
209 static int mimes_cur
;
211 static void mimes_clear(void)
214 memset(mimes_main
, 0, sizeof(mimes_main
));
217 static void mimes_free(struct mail
*mail
)
222 static void mimes_add(struct mail
*mail
)
224 struct mail
*mime_mail
= &mimes_mail
[mimes_cur
];
226 if (mimes_main
[mimes_cur
])
227 mimes_main
[mimes_cur
]->data
= NULL
;
228 mimes_main
[mimes_cur
] = mail
;
229 len
= mime_decode(mimes_buf
[mimes_cur
], mail
->head
,
230 MIN(mail
->len
, MAILBUF
));
231 memset(mime_mail
, 0, sizeof(*mime_mail
));
232 mail_read(mime_mail
, mimes_buf
[mimes_cur
]);
233 /* handle ^From_ inside msgs */
234 mime_mail
->body_len
+= len
- mime_mail
->len
;
235 mime_mail
->len
= len
;
236 mail
->data
= mime_mail
;
237 mimes_cur
= (mimes_cur
+ 1) % MAXMIME
;
240 static struct mail
*mimes_get(struct mail
*mail
)
242 return mail
->data
? mail
->data
: mail
;
245 static void cmd_mime(char *pre
, char *arg
)
248 if (!sel_msgs(pre
, 1))
250 for (i
= 0; i
< nsel
; i
++)
251 mimes_add(sort
->mails
[sel
[i
]]);
254 static void show_mail(char *args
, int filthdr
)
258 char *pg_args
[] = {PAGER
, NULL
};
260 if (!sel_msgs(args
, 0))
262 mail
= sort
->mails
[cur
];
263 mail
->stat
|= STAT_READ
;
264 mail
= mimes_get(mail
);
266 s
+= mail_head(mail
, buf
, sizeof(buf
),
267 hdr_filt
, ARRAY_SIZE(hdr_filt
));
268 s
= put_mem(s
, mail
->body
, MIN(sizeof(buf
) - (s
- buf
),
271 s
= put_mem(s
, mail
->head
, MIN(sizeof(buf
), mail
->len
));
273 exec_pipe(PAGER
, pg_args
, buf
, s
- buf
);
276 static void cmd_page(char *pre
, char *arg
)
281 static void cmd_cat(char *pre
, char *arg
)
286 static char *put_hdr(struct mail
*mail
, char *name
, int w
, char *s
)
288 char *hdr
= mail_hdr(mail
, name
);
290 hdr
= hdr
+ strlen(name
) + 1;
291 if (TRIM_RE
&& tolower(hdr
[0]) == 'r' && tolower(hdr
[1]) == 'e' &&
292 tolower(hdr
[2]) == ':')
296 s
= till_eol(s
, w
, hdr
, hdr
+ hdr_len(hdr
));
301 #define put_clr(s, c) ((COLORS) ? (put_str(s, c)) : (s))
303 static void cmd_head(char *pre
, char *arg
)
307 if (!sel_msgs(pre
, 0))
309 beg
= cur
/ NHEAD
* NHEAD
;
310 end
= MIN(beg
+ NHEAD
, mbox
->n
);
311 for (i
= beg
; i
< end
; i
++) {
312 struct mail
*mail
= sort
->mails
[i
];
317 *s
++ = i
== cur
? '>' : ' ';
318 s
= put_clr(s
, "\33[33m");
319 *s
++ = mail
->stat
& STAT_DEL
? 'D' : ' ';
320 *s
++ = stat_char(mail
);
321 s
= put_clr(s
, "\33[32m");
322 s
= put_int(s
, i
, DIGITS
);
325 mail
= mimes_get(mail
);
326 s
= put_clr(s
, "\33[34m");
327 s
= put_hdr(mail
, "From:", 16, s
);
330 col
= 3 + DIGITS
+ 2 + 16 + 2;
331 indent
= sort_level(sort
, i
) * 2;
333 s
= put_clr(s
, "\33[1;30m");
335 s
= put_clr(s
, "\33[36m");
336 while (indent
-- && col
< WIDTH
) {
340 s
= put_hdr(mail
, "Subject:", WIDTH
- col
, s
);
341 s
= put_clr(s
, "\33[m");
343 write(STDOUT_FILENO
, fmt
, s
- fmt
);
347 static void cmd_z(char *pre
, char *arg
)
351 page
= MIN(cur
+ NHEAD
, mbox
->n
) / NHEAD
;
353 page
= cur
/ NHEAD
- (*(pre
+ 1) ? atoi(pre
+ 1) : 1);
355 page
= cur
/ NHEAD
+ (*(pre
+ 1) ? atoi(pre
+ 1) : 1);
357 page
= mbox
->n
/ NHEAD
;
360 if (page
>= 0 && page
* NHEAD
< mbox
->n
) {
366 static void cmd_del(char *pre
, char *arg
)
369 if (!sel_msgs(pre
, 1))
371 for (i
= 0; i
< nsel
; i
++)
372 sort
->mails
[sel
[i
]]->stat
|= STAT_DEL
;
375 static void cmd_undel(char *pre
, char *arg
)
378 if (!sel_msgs(pre
, 1))
380 for (i
= 0; i
< nsel
; i
++) {
381 sort
->mails
[sel
[i
]]->stat
&= ~STAT_DEL
;
382 mimes_free(sort
->mails
[sel
[i
]]);
386 static void print(char *s
)
388 write(STDOUT_FILENO
, s
, strlen(s
));
391 static int is_mbox(char *filename
)
395 return S_ISREG(st
.st_mode
);
398 static char *filename(char *path
)
400 char *slash
= strrchr(path
, '/');
401 return slash
? slash
+ 1 : path
;
404 static void sum_mbox(void)
412 s
= put_str(s
, filename(mbox
->path
));
413 s
= put_str(s
, ": ");
414 s
= put_int(s
, mbox
->n
, DIGITS
);
415 s
= put_str(s
, " msgs ");
416 for (i
= 0; i
< mbox
->n
; i
++) {
417 if (!(mbox
->mails
[i
].stat
& STAT_READ
)) {
419 if (mbox
->mails
[i
].stat
& STAT_NEW
)
422 if (mbox
->mails
[i
].stat
& STAT_DEL
)
426 s
= put_int(s
, new, DIGITS
);
427 s
= put_str(s
, " new ");
430 s
= put_int(s
, unread
, DIGITS
);
431 s
= put_str(s
, " unread ");
434 s
= put_int(s
, del
, DIGITS
);
435 s
= put_str(s
, " del ");
437 s
= put_str(s
, "\n");
441 static void open_mbox(char *filename
)
443 mbox
= mbox_alloc(filename
);
444 sort
= sort_alloc(mbox
, THREADED
? SORT_THREAD
: 0);
449 static void close_mbox(void)
456 static void mbox_old(struct mbox
*mbox
)
459 for (i
= 0; i
< mbox
->n
; i
++) {
460 struct mail
*mail
= &mbox
->mails
[i
];
461 mail
->stat
= (mail
->stat
& ~STAT_NEW
) | STAT_OLD
;
465 static int has_mail(char *path
)
468 if (stat(path
, &st
) == -1)
470 return st
.st_mtime
> st
.st_atime
;
473 static int mbox_path(char *path
, char *addr
)
478 s
= put_str(s
, FOLDER
);
479 s
= put_str(s
, addr
+ 1);
482 for (i
= 0; i
< ARRAY_SIZE(boxes
); i
++) {
483 if (has_mail(boxes
[i
])) {
484 s
= put_str(s
, boxes
[i
]);
489 if (!strcmp(".", addr
) && mbox
)
490 s
= put_str(s
, mbox
->path
);
491 if (s
== path
&& *addr
)
492 s
= put_str(s
, addr
);
496 static void warn_nomem(void)
498 print("no mem for new msgs\n");
501 static void cmd_fold(char *pre
, char *arg
)
504 char path
[MAXPATHLEN
];
505 if (mbox_path(path
, arg
) && is_mbox(path
)) {
507 if (mbox_write(mbox
) == -1) {
519 static void cmd_news(char *pre
, char *arg
)
524 for (i
= 0; i
< ARRAY_SIZE(boxes
); i
++) {
525 if (has_mail(boxes
[i
])) {
527 s
= put_str(s
, "\t");
528 s
= put_str(s
, filename(boxes
[i
]));
529 s
= put_str(s
, "\n");
535 static void cmd_inc(char *pre
, char *arg
)
538 int new = mbox_inc(mbox
);
545 s
= put_int(s
, new, DIGITS
);
546 s
= put_str(s
, " new\n");
551 static void cmd_next(char *pre
, char *arg
)
554 if (cur
+ 1 < mbox
->n
) {
564 static void copy_mail(char *msgs
, char *dst
, int del
)
571 if (!sel_msgs(msgs
, 1))
573 mbox_path(path
, dst
);
574 fd
= open(path
, O_WRONLY
| O_APPEND
| O_CREAT
, S_IRUSR
| S_IWUSR
);
576 print("failed to open mbox for writing\n");
579 for (i
= 0; i
< nsel
; i
++) {
580 struct mail
*mail
= sort
->mails
[sel
[i
]];
581 mail_write(mail
, fd
);
583 mail
->stat
|= STAT_DEL
;
588 static void cmd_copy(char *pre
, char *arg
)
590 copy_mail(pre
, arg
, 0);
593 static void cmd_move(char *pre
, char *arg
)
595 copy_mail(pre
, arg
, 1);
598 static void compose(struct draft
*draft
)
600 char record
[MAXPATHLEN
] = "";
603 char *pg_args
[] = {PAGER
, NULL
};
605 mbox_path(record
, RECORD
);
607 strcpy(record
, mbox
->path
);
608 while (read_line(line
, sizeof(line
)) > 0) {
610 if (!strcmp("~e", cmd
))
611 draft_edit(draft
, EDITOR
);
612 if (!strcmp("~v", cmd
))
613 draft_edit(draft
, VISUAL
);
614 if (!strcmp("~.", cmd
)) {
616 draft_save(draft
, record
);
620 if (!strcmp("~p", cmd
))
621 exec_pipe(PAGER
, pg_args
, draft
->mail
, draft
->len
);
622 if (!strcmp("~q", cmd
) || !strcmp("~x", cmd
))
627 static void cmd_mail(char *pre
, char *arg
)
630 draft_init(&draft
, *arg
? &arg
: NULL
, *arg
? 1 : 0, NULL
);
634 static void cmd_reply(char *pre
, char *arg
)
637 if (!sel_msgs(pre
, 0))
639 draft_reply(&draft
, mimes_get(sort
->mails
[cur
]));
643 static void prompt(void)
645 write(STDOUT_FILENO
, "? ", 2);
648 static void cmd_quit(char *pre
, char *arg
)
651 if (mbox_write(mbox
) == -1)
657 static void cmd_exit(char *pre
, char *arg
)
664 void (*cmd
)(char *pre
, char *arg
);
668 {"header", cmd_head
},
671 {"folder", cmd_fold
},
679 {"reply", cmd_reply
},
684 {"undelete", cmd_undel
},
692 static void cmd_parse(char *line
, char *pre
, char *cmd
, char *arg
)
694 while (*line
&& isspace(*line
))
696 while (*line
&& !isalpha(*line
)) {
697 if (line
[0] == '/' && (line
[1] == '(' || line
[2] == '('))
698 while (*line
&& *line
!= ')')
703 while (*line
&& isspace(*line
))
705 while (*line
&& isalpha(*line
))
708 while (*line
&& isspace(*line
))
710 while (*line
&& !isspace(*line
))
715 static void loop(void)
723 while (read_line(line
, sizeof(line
)) > 0) {
724 cmd_parse(line
, pre
, cmd
, arg
);
730 for (i
= 0; i
< ARRAY_SIZE(cmds
); i
++)
731 if (!strncmp(cmds
[i
].name
, cmd
, len
)) {
732 cmds
[i
].cmd(pre
, arg
);
742 int main(int argc
, char *argv
[])
745 char *filename
= NULL
;
748 if (argv
[i
][0] != '-')
750 if (!strcmp("-f", argv
[i
]))
751 filename
= argv
[++i
];
752 if (!strcmp("-s", argv
[i
]))
756 char path
[MAXPATHLEN
];
757 if (mbox_path(path
, filename
) && is_mbox(path
)) {
764 draft_init(&draft
, argv
+ i
, argc
- i
, subj
);