mime: fix uninitialised mime->depth
[neatmail.git] / mk.c
blobb87b95e73bead3bd684b5320f7728952036cf792
1 #include <ctype.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <time.h>
6 #include "mail.h"
8 #define LEN(a) (sizeof(a) / sizeof((a)[0]))
10 static int uc_len(char *s)
12 int c = (unsigned char) s[0];
13 if (~c & 0x80)
14 return c > 0;
15 if (~c & 0x20)
16 return 2;
17 if (~c & 0x10)
18 return 3;
19 if (~c & 0x80)
20 return 4;
21 if (~c & 0x40)
22 return 5;
23 if (~c & 0x20)
24 return 6;
25 return 1;
28 static int uc_wid(char *s)
30 return 1;
33 static char *msg_dec(char *msg, long msz, char *hdr)
35 char *val = msg_get(msg, msz, hdr);
36 char *buf, *ret;
37 int val_len;
38 if (!val)
39 return NULL;
40 val_len = hdrlen(val, msg + msz - val) - 1;
41 buf = malloc(val_len + 1);
42 memcpy(buf, val, val_len);
43 buf[val_len] = '\0';
44 ret = msg_hdrdec(buf);
45 free(buf);
46 return ret;
49 static int msg_stat(char *msg, long msz)
51 char *val = msg_get(msg, msz, "status:");
52 if (!val)
53 return 'N';
54 val += strlen("status:");
55 while (isspace((unsigned char) val[0]))
56 val++;
57 return val[0];
60 static int datedate(char *s);
62 static char *fieldformat(char *msg, long msz, char *hdr, int wid)
64 char tbuf[128];
65 struct sbuf *dst;
66 int dst_wid;
67 char *val, *val0, *end;
68 val = msg_dec(msg, msz, hdr[0] == '~' ? hdr + 1 : hdr);
69 if (!val) {
70 val = malloc(1);
71 val[0] = '\0';
73 val0 = val;
74 end = strchr(val, '\0');
75 dst = sbuf_make();
76 val += strlen(hdr);
77 while (val < end && isspace((unsigned char) *val))
78 val++;
79 dst_wid = 0;
80 if (!strcmp("~subject:", hdr)) {
81 while (startswith(val, "re:") || startswith(val, "fwd:")) {
82 sbuf_chr(dst, '+');
83 dst_wid++;
84 val = strchr(val, ':') + 1;
85 while (val < end && isspace((unsigned char) *val))
86 val++;
89 if (!strcmp("~date:", hdr)) {
90 time_t ts = datedate(val);
91 strftime(tbuf, sizeof(tbuf), "%d %b %Y %H:%M:%S", localtime(&ts));
92 val = tbuf;
93 end = strchr(tbuf, '\0');
95 while (val < end && (wid <= 0 || dst_wid < wid)) {
96 int l = uc_len(val);
97 if (l == 1) {
98 int c = (unsigned char) *val;
99 sbuf_chr(dst, isblank(c) || !isprint(c) ? ' ' : c);
100 } else {
101 sbuf_mem(dst, val, l);
103 dst_wid += uc_wid(val);
104 val += l;
106 if (wid > 0)
107 while (dst_wid++ < wid)
108 sbuf_chr(dst, ' ');
109 free(val0);
110 return sbuf_done(dst);
113 static void mk_sum(struct mbox *mbox)
115 int stats[128] = {0};
116 char *msg;
117 long msz;
118 int i, st;
119 for (i = 0; i < mbox_len(mbox); i++) {
120 mbox_get(mbox, i, &msg, &msz);
121 st = msg_stat(msg, msz);
122 if (st >= 'A' && st <= 'Z')
123 stats[st - 'A']++;
125 for (i = 0; i < LEN(stats); i++)
126 if (stats[i])
127 fprintf(stderr, "%c%04d ", 'A' + i, stats[i]);
128 fprintf(stderr, "\n");
131 static char *segment(char *d, char *s, int m)
133 char *r = strchr(s, m);
134 char *e = r ? r + 1 : strchr(s, '\0');
135 memcpy(d, s, e - s);
136 d[e - s] = '\0';
137 return e;
140 static char *usage =
141 "usage: neatmail mk [options] mbox\n\n"
142 "options:\n"
143 " -0 fmt \tmessage first line format (e.g., 20from:40subject:)\n"
144 " -1 fmt \tmessage second line format\n"
145 " -sd \tsort by receiving date\n"
146 " -st \tsort by threads\n"
147 " -r \tprint a summary of status flags\n"
148 " -f n \tthe first message to list\n";
150 static int sort_mails(struct mbox *mbox, int *mids, int *levs);
152 int mk(char *argv[])
154 int *mids, *levs;
155 struct mbox *mbox;
156 char *ln[4] = {"18from:40~subject:"};
157 int i, j, k;
158 int beg = 0;
159 int sort = 0;
160 int sum = 0;
161 for (i = 0; argv[i] && argv[i][0] == '-'; i++) {
162 if (argv[i][1] == 'f') {
163 beg = atoi(argv[i][2] ? argv[i] + 2 : argv[++i]);
164 continue;
166 if (argv[i][1] == 'r') {
167 sum = 1;
168 continue;
170 if (argv[i][1] == 's') {
171 char t = (argv[i][2] ? argv[i] + 2 : argv[++i])[0];
172 sort = t == 't' ? 2 : 1;
173 continue;
175 if (argv[i][1] == '0' || argv[i][1] == '1') {
176 int idx = argv[i][1] - '0';
177 ln[idx] = argv[i][2] ? argv[i] + 2 : argv[++i];
178 continue;
181 if (!argv[i]) {
182 printf("%s", usage);
183 return 1;
185 mbox = mbox_open(argv[i]);
186 if (!mbox) {
187 fprintf(stderr, "neatmail: cannot open <%s>\n", argv[i]);
188 return 1;
190 mids = malloc(mbox_len(mbox) * sizeof(mids[0]));
191 levs = malloc(mbox_len(mbox) * sizeof(levs[0]));
192 for (i = 0; i < mbox_len(mbox); i++)
193 mids[i] = i;
194 for (i = 0; i < mbox_len(mbox); i++)
195 levs[i] = 0;
196 if (sort)
197 sort_mails(mbox, mids, sort == 2 ? levs : NULL);
198 for (i = beg; i < mbox_len(mbox); i++) {
199 char *msg;
200 long msz;
201 mbox_get(mbox, mids[i], &msg, &msz);
202 printf("%c%04d", msg_stat(msg, msz), mids[i]);
203 for (j = 0; ln[j]; j++) {
204 char *cln = ln[j];
205 char *tok = malloc(strlen(ln[j]) + 1);
206 if (j)
207 printf("\n");
208 while ((cln = segment(tok, cln, ':')) && tok[0]) {
209 char *fmt = tok;
210 char *hdr = tok;
211 char *val;
212 int wid = atoi(fmt);
213 while (isdigit((unsigned char) *hdr))
214 hdr++;
215 printf("\t");
216 if (!strcmp("~subject:", hdr))
217 for (k = 0; k < levs[i]; k++, wid--)
218 printf(" ");
219 val = fieldformat(msg, msz, hdr, wid);
220 printf("[%s]", val);
221 free(val);
223 free(tok);
225 printf("\n");
227 free(mids);
228 free(levs);
229 if (sum)
230 mk_sum(mbox);
231 mbox_free(mbox);
232 return 0;
235 /* sorting messages */
237 struct msg {
238 char *msg;
239 long msglen;
240 char *id; /* message-id header value */
241 int id_len; /* message-id length */
242 char *rply; /* reply-to header value */
243 int rply_len; /* reply-to length */
244 int date; /* message receiving date */
245 int depth; /* depth of message in the thread */
246 int oidx; /* the original index of the message */
247 struct msg *parent;
248 struct msg *head;
249 struct msg *tail;
250 struct msg *next;
253 static int id_cmp(char *i1, int l1, char *i2, int l2)
255 if (l1 != l2)
256 return l2 - l1;
257 return strncmp(i1, i2, l1);
260 static int msgcmp_id(void *v1, void *v2)
262 struct msg *t1 = *(struct msg **) v1;
263 struct msg *t2 = *(struct msg **) v2;
264 return id_cmp(t1->id, t1->id_len, t2->id, t2->id_len);
267 static int msgcmp_date(void *v1, void *v2)
269 struct msg *t1 = *(struct msg **) v1;
270 struct msg *t2 = *(struct msg **) v2;
271 return t1->date == t2->date ? t1->oidx - t2->oidx : t1->date - t2->date;
274 static char *months[] = {
275 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
276 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
279 static char *readtok(char *s, char *d)
281 while (*s == ' ' || *s == ':')
282 s++;
283 while (!strchr(" \t\r\n:", *s))
284 *d++ = *s++;
285 *d = '\0';
286 return s;
289 static int fromdate(char *s)
291 char tok[128];
292 int year, mon, day, hour, min, sec;
293 int i;
294 /* parsing "From ali Tue Apr 16 20:18:40 2013" */
295 s = readtok(s, tok); /* From */
296 s = readtok(s, tok); /* username */
297 s = readtok(s, tok); /* day of week */
298 s = readtok(s, tok); /* month name */
299 mon = 0;
300 for (i = 0; i < LEN(months); i++)
301 if (!strcmp(months[i], tok))
302 mon = i;
303 s = readtok(s, tok); /* day of month */
304 day = atoi(tok);
305 s = readtok(s, tok); /* hour */
306 hour = atoi(tok);
307 s = readtok(s, tok); /* minute */
308 min = atoi(tok);
309 s = readtok(s, tok); /* seconds */
310 sec = atoi(tok);
311 s = readtok(s, tok); /* year */
312 year = atoi(tok);
313 return ((year - 1970) * 400 + mon * 31 + day) * 24 * 3600 +
314 hour * 3600 + min * 60 + sec;
317 static int datedate(char *s)
319 char tok[128];
320 struct tm tm = {0};
321 int ts, tz, i;
322 /* parsing "Fri, 25 Dec 2015 20:26:18 +0100" */
323 s = readtok(s, tok); /* day of week */
324 s = readtok(s, tok); /* day of month */
325 tm.tm_mday = atoi(tok);
326 s = readtok(s, tok); /* month name */
327 for (i = 0; i < LEN(months); i++)
328 if (!strcmp(months[i], tok))
329 tm.tm_mon = i;
330 s = readtok(s, tok); /* year */
331 tm.tm_year = atoi(tok) - 1900;
332 s = readtok(s, tok); /* hour */
333 tm.tm_hour = atoi(tok);
334 s = readtok(s, tok); /* minute */
335 tm.tm_min = atoi(tok);
336 s = readtok(s, tok); /* seconds */
337 tm.tm_sec = atoi(tok);
338 s = readtok(s, tok); /* time-zone */
339 tz = atoi(tok);
340 ts = mktime(&tm);
341 if (tz >= 0)
342 ts -= (tz / 100) * 3600 + (tz % 100) * 60;
343 else
344 ts += (-tz / 100) * 3600 + (-tz % 100) * 60;
345 return ts;
348 static struct msg *msg_byid(struct msg **msgs, int n, char *id, int len)
350 int l = 0;
351 int h = n;
352 while (l < h) {
353 int m = (l + h) / 2;
354 int d = id_cmp(id, len, msgs[m]->id, msgs[m]->id_len);
355 if (!d)
356 return msgs[m];
357 if (d < 0)
358 h = m;
359 else
360 l = m + 1;
362 return NULL;
365 static void msgs_tree(struct msg **all, struct msg **sorted_id, int n)
367 int i;
368 for (i = 0; i < n; i++) {
369 struct msg *msg = all[i];
370 struct msg *dad;
371 if (!msg->rply)
372 continue;
373 dad = msg_byid(sorted_id, n, msg->rply, msg->rply_len);
374 if (!dad)
375 continue;
376 msg->parent = dad;
377 msg->depth = dad->depth + 1;
378 if (!msg->parent->head)
379 msg->parent->head = msg;
380 else
381 msg->parent->tail->next = msg;
382 msg->parent->tail = msg;
386 static void msg_init(struct msg *msg)
388 char *id_hdr = msg_get(msg->msg, msg->msglen, "Message-ID:");
389 char *rply_hdr = msg_get(msg->msg, msg->msglen, "In-Reply-To:");
390 char *end = msg->msg + msg->msglen;
391 if (id_hdr) {
392 int len = hdrlen(id_hdr, end - id_hdr);
393 char *beg = memchr(id_hdr, '<', len);
394 char *end = beg ? memchr(id_hdr, '>', len) : NULL;
395 if (beg && end) {
396 while (*beg == '<')
397 beg++;
398 msg->id = beg;
399 msg->id_len = end - beg;
402 if (rply_hdr) {
403 int len = hdrlen(rply_hdr, end - rply_hdr);
404 char *beg = memchr(rply_hdr, '<', len);
405 char *end = beg ? memchr(rply_hdr, '>', len) : NULL;
406 if (beg && end) {
407 while (*beg == '<')
408 beg++;
409 msg->rply = beg;
410 msg->rply_len = end - beg;
413 msg->date = fromdate(msg->msg);
416 static struct msg **put_msg(struct msg **sorted, struct msg *msg)
418 struct msg *cur = msg->head;
419 *sorted++ = msg;
420 while (cur) {
421 sorted = put_msg(sorted, cur);
422 cur = cur->next;
424 return sorted;
427 static void msgs_sort(struct msg **sorted, struct msg **msgs, int n)
429 int i;
430 for (i = 0; i < n; i++)
431 if (!msgs[i]->parent)
432 sorted = put_msg(sorted, msgs[i]);
435 static int sort_mails(struct mbox *mbox, int *mids, int *levs)
437 int n = mbox_len(mbox);
438 struct msg *msgs = malloc(n * sizeof(*msgs));
439 struct msg **sorted_date = malloc(n * sizeof(*sorted_date));
440 struct msg **sorted_id = malloc(n * sizeof(*sorted_id));
441 struct msg **sorted = malloc(n * sizeof(*sorted));
442 int i;
443 if (!msgs || !sorted_date || !sorted_id) {
444 free(msgs);
445 free(sorted_date);
446 free(sorted_id);
447 free(sorted);
448 return 1;
450 memset(msgs, 0, n * sizeof(*msgs));
451 for (i = 0; i < n; i++) {
452 struct msg *msg = &msgs[i];
453 msg->oidx = i;
454 mbox_get(mbox, i, &msg->msg, &msg->msglen);
455 sorted_id[i] = msg;
456 sorted_date[i] = msg;
457 msg_init(msg);
459 qsort(sorted_date, n, sizeof(*sorted_date), (void *) msgcmp_date);
460 qsort(sorted_id, n, sizeof(*sorted_id), (void *) msgcmp_id);
461 if (levs)
462 msgs_tree(sorted_date, sorted_id, n);
463 msgs_sort(sorted, sorted_date, n);
464 for (i = 0; i < n; i++)
465 mids[i] = sorted[i]->oidx;
466 if (levs)
467 for (i = 0; i < n; i++)
468 levs[i] = sorted[i]->depth;
469 free(msgs);
470 free(sorted_date);
471 free(sorted_id);
472 free(sorted);
473 return 0;