]> mj.ucw.cz Git - maildups.git/blob - mparse.c
Teach mparse to handle maildirs
[maildups.git] / mparse.c
1 /*
2  *      Mailbox and Maildir Parser (so far very primitive)
3  *
4  *      (c) 2010--2018 Martin Mares <mj@ucw.cz>
5  */
6
7 #include <stdio.h>
8 #include <string.h>
9 #include <stdlib.h>
10 #include <dirent.h>
11 #include <fcntl.h>
12 #include <unistd.h>
13 #include <sys/stat.h>
14
15 #include "util.h"
16
17 const char progname[] = "mparse";
18 static char **field_names;
19 static int num_fields;
20 static int mbox_fd;
21
22 #define BUFSIZE 65536
23
24 static char buf[BUFSIZE];
25 static char *bpos, *bend;
26
27 static void reset_buf(void)
28 {
29   bpos = buf;
30   bend = buf;
31 }
32
33 static char *get_line(void)
34 {
35   if (!bpos)
36     return NULL;
37
38   for (;;)
39     {
40       int remains = bend - bpos;
41       if (remains)
42         {
43           char *nl = memchr(bpos, '\n', remains);
44           if (nl)
45             {
46               char *p = bpos;
47               if (nl > p && nl[-1] == '\r')
48                 nl[-1] = 0;
49               *nl++ = 0;
50               bpos = nl;
51               return p;
52             }
53           memmove(buf, bpos, remains);
54         }
55       bpos = buf;
56       bend = buf + remains;
57
58       int space = BUFSIZE - remains;
59       if (!space)
60         die("Line too long");
61       int len = read(mbox_fd, bend, space);
62       if (len < 0)
63         die("Read error: %m");
64       if (!len)
65         {
66           bpos = NULL;
67           if (bend > buf)
68             {
69               *bend = 0;
70               return buf;
71             }
72           return NULL;
73         }
74       bend = bend + len;
75     }
76 }
77
78 static int want_header(char *line)
79 {
80   if (!num_fields)
81     return 1;
82
83   for (int i=0; i<num_fields; i++)
84     {
85       int len = strlen(field_names[i]);
86       if (!strncasecmp(line, field_names[i], len))
87         return 1;
88     }
89
90   return 0;
91 }
92
93 enum state {
94   S_INITIAL,
95   S_HEADER,
96   S_BODY,
97 };
98
99 static void parse_mbox(void)
100 {
101   char *c;
102   enum state state = S_INITIAL;
103   int want = 0;
104
105   reset_buf();
106   while (c = get_line())
107     {
108       // printf("<%s>\n", c);
109       if (state == S_INITIAL || state == S_BODY)
110         {
111           if (!strncmp(c, "From ", 5))
112             {
113               state = S_HEADER;
114               fputs(c, stdout);
115             }
116         }
117       else
118         {
119           if (!*c)
120             {
121               puts("\n");
122               state = S_BODY;
123             }
124           else if (*c == ' ' || *c == '\t')
125             {
126               if (want)
127                 {
128                   while (*c == ' ' || *c == '\t')
129                     c++;
130                   putchar(' ');
131                   fputs(c, stdout);
132                 }
133             }
134           else
135             {
136               want = want_header(c);
137               if (want)
138                 {
139                   putchar('\n');
140                   fputs(c, stdout);
141                 }
142             }
143         }
144     }
145
146   if (state == S_HEADER)
147     printf("\nX-Warning: Incomplete mail\n\n");
148 }
149
150 static void parse_maildir_file(void)
151 {
152   char *c;
153   int want = 0;
154
155   reset_buf();
156   while (c = get_line())
157     {
158       // printf("<%s>\n", c);
159       if (!*c)
160         {
161           putchar('\n');
162           return;
163         }
164       else if (*c == ' ' || *c == '\t')
165         {
166           if (want)
167             {
168               while (*c == ' ' || *c == '\t')
169                 c++;
170               putchar(' ');
171               fputs(c, stdout);
172             }
173         }
174       else
175         {
176           want = want_header(c);
177           if (want)
178             {
179               putchar('\n');
180               fputs(c, stdout);
181             }
182         }
183     }
184
185   printf("\nX-Warning: Incomplete mail\n\n");
186 }
187
188 static void parse_maildir(char *dir, char *subdir)
189 {
190   char buf[4096];
191   snprintf(buf, sizeof(buf), "%s/%s", dir, subdir);
192
193   DIR *d = opendir(buf);
194   if (!d)
195     return;
196
197   struct dirent *e;
198   while (e = readdir(d))
199     {
200       if (e->d_name[0] == '.')
201         continue;
202       snprintf(buf, sizeof(buf), "%s/%s/%s", dir, subdir, e->d_name);
203       mbox_fd = open(buf, O_RDONLY);
204       if (mbox_fd < 0)
205         continue;
206       parse_maildir_file();
207       close(mbox_fd);
208     }
209
210   closedir(d);
211 }
212
213 static void usage(void)
214 {
215   fprintf(stderr, "Usage: mparse [-f <mailbox>] <header-field> ...\n");
216   exit(1);
217 }
218
219 int
220 main(int argc, char **argv)
221 {
222   int opt;
223   char *folder = NULL;
224
225   while ((opt = getopt(argc, argv, "f:")) >= 0)
226     switch (opt)
227       {
228       case 'f':
229         folder = optarg;
230         break;
231       default:
232         usage();
233       }
234
235   field_names = argv + optind;
236   num_fields = argc - optind;
237
238   if (!folder)
239     {
240       parse_mbox();
241       return 0;
242     }
243
244   struct stat st;
245   if (stat(folder, &st) < 0)
246     die("Cannot stat %s: %m", folder);
247
248   if (st.st_mode & S_IFDIR)
249     {
250       parse_maildir(folder, "cur");
251       parse_maildir(folder, "new");
252     }
253   else
254     {
255       mbox_fd = open(folder, O_RDONLY);
256       if (mbox_fd < 0)
257         die("Cannot open %s: %m", folder);
258       parse_mbox();
259       close(mbox_fd);
260     }
261
262   return 0;
263 }