mbox: check the message number in mbox_set()
[neatmail.git] / mk.c
blobde243c4aa59598a6ae891c53bf88b58f8deab3fe
1 #include <ctype.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include "mail.h"
7 #define LEN(a) (sizeof(a) / sizeof((a)[0]))
9 static int uc_len(char *s)
11 int c = (unsigned char) s[0];
12 if (~c & 0x80)
13 return c > 0;
14 if (~c & 0x20)
15 return 2;
16 if (~c & 0x10)
17 return 3;
18 if (~c & 0x80)
19 return 4;
20 if (~c & 0x40)
21 return 5;
22 if (~c & 0x20)
23 return 6;
24 return 1;
27 static int uc_wid(char *s)
29 return 1;
32 static char *msg_dec(char *msg, long msz, char *hdr)
34 char *val = msg_get(msg, msz, hdr);
35 char *buf, *ret;
36 int val_len;
37 if (!val)
38 return NULL;
39 val_len = hdrlen(val, msg + msz - val) - 1;
40 buf = malloc(val_len + 1);
41 memcpy(buf, val, val_len);
42 buf[val_len] = '\0';
43 ret = msg_hdrdec(buf);
44 free(buf);
45 return ret;
48 static int msg_stat(char *msg, long msz)
50 char *val = msg_get(msg, msz, "status:");
51 if (!val)
52 return 'N';
53 val += strlen("status:");
54 while (isspace((unsigned char) val[0]))
55 val++;
56 return val[0];
59 static char *fieldformat(char *msg, long msz, char *hdr, int wid)
61 struct sbuf *dst;
62 int dst_wid;
63 char *val, *val0, *end;
64 val = msg_dec(msg, msz, hdr[0] == '~' ? hdr + 1 : hdr);
65 if (!val) {
66 val = malloc(1);
67 val[0] = '\0';
69 val0 = val;
70 end = strchr(val, '\0');
71 dst = sbuf_make();
72 val += strlen(hdr);
73 while (val < end && isspace((unsigned char) *val))
74 val++;
75 dst_wid = 0;
76 if (!strcmp("~subject:", hdr)) {
77 while (startswith(val, "re:") || startswith(val, "fwd:")) {
78 sbuf_chr(dst, '+');
79 dst_wid++;
80 val = strchr(val, ':') + 1;
81 while (val < end && isspace((unsigned char) *val))
82 val++;
85 while (val < end && (wid <= 0 || dst_wid < wid)) {
86 int l = uc_len(val);
87 if (l == 1) {
88 int c = (unsigned char) *val;
89 sbuf_chr(dst, isblank(c) || !isprint(c) ? ' ' : c);
90 } else {
91 sbuf_mem(dst, val, l);
93 dst_wid += uc_wid(val);
94 val += l;
96 if (wid > 0)
97 while (dst_wid++ < wid)
98 sbuf_chr(dst, ' ');
99 free(val0);
100 return sbuf_done(dst);
103 static char *segment(char *d, char *s, int m)
105 char *r = strchr(s, m);
106 char *e = r ? r + 1 : strchr(s, '\0');
107 memcpy(d, s, e - s);
108 d[e - s] = '\0';
109 return e;
112 static char *usage =
113 "usage: neatmail mk [options] mbox\n\n"
114 "options:\n"
115 " -0 fmt \tmessage first line format (e.g., 20from:40subject:)\n"
116 " -1 fmt \tmessage second line format\n"
117 " -sd \tsort by receiving date\n"
118 " -st \tsort by threads\n"
119 " -f n \tthe first message to list\n";
121 static int sort_mails(struct mbox *mbox, int *mids, int *levs);
123 int mk(char *argv[])
125 int *mids, *levs;
126 struct mbox *mbox;
127 char *ln[4] = {"18from:40~subject:"};
128 int i, j, k;
129 int first = 0;
130 int sort = 0;
131 for (i = 0; argv[i] && argv[i][0] == '-'; i++) {
132 if (argv[i][1] == 'f') {
133 first = atoi(argv[i][2] ? argv[i] + 2 : argv[++i]);
134 continue;
136 if (argv[i][1] == 's') {
137 char t = (argv[i][2] ? argv[i] + 2 : argv[++i])[0];
138 sort = t == 't' ? 2 : 1;
139 continue;
141 if (argv[i][1] == '0' || argv[i][1] == '1') {
142 int idx = argv[i][1] - '0';
143 ln[idx] = argv[i][2] ? argv[i] + 2 : argv[++i];
144 continue;
147 if (!argv[i]) {
148 printf("%s", usage);
149 return 1;
151 mbox = mbox_open(argv[i]);
152 if (!mbox) {
153 fprintf(stderr, "neatmail: cannot open <%s>\n", argv[i]);
154 return 1;
156 mids = malloc(mbox_len(mbox) * sizeof(mids[0]));
157 levs = malloc(mbox_len(mbox) * sizeof(levs[0]));
158 for (i = 0; i < mbox_len(mbox); i++)
159 mids[i] = i;
160 for (i = 0; i < mbox_len(mbox); i++)
161 levs[i] = 0;
162 if (sort)
163 sort_mails(mbox, mids, sort == 2 ? levs : NULL);
164 for (i = first; i < mbox_len(mbox); i++) {
165 char *msg;
166 long msz;
167 mbox_get(mbox, mids[i], &msg, &msz);
168 printf("%c%04d", msg_stat(msg, msz), mids[i]);
169 for (j = 0; ln[j]; j++) {
170 char *cln = ln[j];
171 char *tok = malloc(strlen(ln[j]) + 1);
172 if (j)
173 printf("\n");
174 while ((cln = segment(tok, cln, ':')) && tok[0]) {
175 char *fmt = tok;
176 char *hdr = tok;
177 char *val;
178 int wid = atoi(fmt);
179 while (isdigit((unsigned char) *hdr))
180 hdr++;
181 printf("\t");
182 if (!strcmp("~subject:", hdr))
183 for (k = 0; k < levs[i]; k++, wid--)
184 printf(" ");
185 val = fieldformat(msg, msz, hdr, wid);
186 printf("[%s]", val);
187 free(val);
189 free(tok);
191 printf("\n");
193 free(mids);
194 free(levs);
195 mbox_free(mbox);
196 return 0;
199 /* sorting messages */
201 struct msg {
202 char *msg;
203 long msglen;
204 char *id; /* message-id header value */
205 int id_len; /* message-id length */
206 char *rply; /* reply-to header value */
207 int rply_len; /* reply-to length */
208 int date; /* message receiving date */
209 int depth; /* depth of message in the thread */
210 int oidx; /* the original index of the message */
211 struct msg *parent;
212 struct msg *head;
213 struct msg *tail;
214 struct msg *next;
217 static int id_cmp(char *i1, int l1, char *i2, int l2)
219 if (l1 != l2)
220 return l2 - l1;
221 return strncmp(i1, i2, l1);
224 static int msgcmp_id(void *v1, void *v2)
226 struct msg *t1 = *(struct msg **) v1;
227 struct msg *t2 = *(struct msg **) v2;
228 return id_cmp(t1->id, t1->id_len, t2->id, t2->id_len);
231 static int msgcmp_date(void *v1, void *v2)
233 struct msg *t1 = *(struct msg **) v1;
234 struct msg *t2 = *(struct msg **) v2;
235 return t1->date == t2->date ? t1->oidx - t2->oidx : t1->date - t2->date;
238 static char *months[] = {
239 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
240 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
243 static char *readtok(char *s, char *d)
245 while (*s == ' ' || *s == ':')
246 s++;
247 while (!strchr(" \t\r\n:", *s))
248 *d++ = *s++;
249 *d = '\0';
250 return s;
253 static int fromdate(char *s)
255 char tok[128];
256 int year, mon, day, hour, min, sec;
257 int i;
258 /* parsing "From ali Tue Apr 16 20:18:40 2013" */
259 s = readtok(s, tok); /* From */
260 s = readtok(s, tok); /* username */
261 s = readtok(s, tok); /* day of week */
262 s = readtok(s, tok); /* month name */
263 mon = 0;
264 for (i = 0; i < LEN(months); i++)
265 if (!strcmp(months[i], tok))
266 mon = i;
267 s = readtok(s, tok); /* day of month */
268 day = atoi(tok);
269 s = readtok(s, tok); /* hour */
270 hour = atoi(tok);
271 s = readtok(s, tok); /* minute */
272 min = atoi(tok);
273 s = readtok(s, tok); /* seconds */
274 sec = atoi(tok);
275 s = readtok(s, tok); /* year */
276 year = atoi(tok);
277 return ((year - 1970) * 400 + mon * 31 + day) * 24 * 3600 +
278 hour * 3600 + min * 60 + sec;
281 static struct msg *msg_byid(struct msg **msgs, int n, char *id, int len)
283 int l = 0;
284 int h = n;
285 while (l < h) {
286 int m = (l + h) / 2;
287 int d = id_cmp(id, len, msgs[m]->id, msgs[m]->id_len);
288 if (!d)
289 return msgs[m];
290 if (d < 0)
291 h = m;
292 else
293 l = m + 1;
295 return NULL;
298 static void msgs_tree(struct msg **all, struct msg **sorted_id, int n)
300 int i;
301 for (i = 0; i < n; i++) {
302 struct msg *msg = all[i];
303 struct msg *dad;
304 if (!msg->rply)
305 continue;
306 dad = msg_byid(sorted_id, n, msg->rply, msg->rply_len);
307 if (!dad)
308 continue;
309 msg->parent = dad;
310 msg->depth = dad->depth + 1;
311 if (!msg->parent->head)
312 msg->parent->head = msg;
313 else
314 msg->parent->tail->next = msg;
315 msg->parent->tail = msg;
319 static void msg_init(struct msg *msg)
321 char *id_hdr = msg_get(msg->msg, msg->msglen, "Message-ID:");
322 char *rply_hdr = msg_get(msg->msg, msg->msglen, "In-Reply-To:");
323 char *end = msg->msg + msg->msglen;
324 if (id_hdr) {
325 int len = hdrlen(id_hdr, end - id_hdr);
326 char *beg = memchr(id_hdr, '<', len);
327 char *end = beg ? memchr(id_hdr, '>', len) : NULL;
328 if (beg && end) {
329 while (*beg == '<')
330 beg++;
331 msg->id = beg;
332 msg->id_len = end - beg;
335 if (rply_hdr) {
336 int len = hdrlen(rply_hdr, end - rply_hdr);
337 char *beg = memchr(rply_hdr, '<', len);
338 char *end = beg ? memchr(rply_hdr, '>', len) : NULL;
339 if (beg && end) {
340 while (*beg == '<')
341 beg++;
342 msg->rply = beg;
343 msg->rply_len = end - beg;
346 msg->date = fromdate(msg->msg);
349 static struct msg **put_msg(struct msg **sorted, struct msg *msg)
351 struct msg *cur = msg->head;
352 *sorted++ = msg;
353 while (cur) {
354 sorted = put_msg(sorted, cur);
355 cur = cur->next;
357 return sorted;
360 static void msgs_sort(struct msg **sorted, struct msg **msgs, int n)
362 int i;
363 for (i = 0; i < n; i++)
364 if (!msgs[i]->parent)
365 sorted = put_msg(sorted, msgs[i]);
368 static int sort_mails(struct mbox *mbox, int *mids, int *levs)
370 int n = mbox_len(mbox);
371 struct msg *msgs = malloc(n * sizeof(*msgs));
372 struct msg **sorted_date = malloc(n * sizeof(*sorted_date));
373 struct msg **sorted_id = malloc(n * sizeof(*sorted_id));
374 struct msg **sorted = malloc(n * sizeof(*sorted));
375 int i;
376 if (!msgs || !sorted_date || !sorted_id) {
377 free(msgs);
378 free(sorted_date);
379 free(sorted_id);
380 free(sorted);
381 return 1;
383 memset(msgs, 0, n * sizeof(*msgs));
384 for (i = 0; i < n; i++) {
385 struct msg *msg = &msgs[i];
386 msg->oidx = i;
387 mbox_get(mbox, i, &msg->msg, &msg->msglen);
388 sorted_id[i] = msg;
389 sorted_date[i] = msg;
390 msg_init(msg);
392 qsort(sorted_date, n, sizeof(*sorted_date), (void *) msgcmp_date);
393 qsort(sorted_id, n, sizeof(*sorted_id), (void *) msgcmp_id);
394 if (levs)
395 msgs_tree(sorted_date, sorted_id, n);
396 msgs_sort(sorted, sorted_date, n);
397 for (i = 0; i < n; i++)
398 mids[i] = sorted[i]->oidx;
399 if (levs)
400 for (i = 0; i < n; i++)
401 levs[i] = sorted[i]->depth;
402 free(msgs);
403 free(sorted_date);
404 free(sorted_id);
405 free(sorted);
406 return 0;