Many fixes and optimisations:
[banana.git] / mbox-helper / mbox-helper.c
1 /** Read an mbox
2 */
3
4 #include <unistd.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <ctype.h>
9 #include <locale.h>
10
11 /** Macros
12 */
13 #define LTRIM(pos) while (isspace(*pos)) { pos++; }
14 #define STRTOLOWER(str, ptr) for (ptr = str ; *ptr ; ptr++) { *ptr = tolower(*ptr); }
15
16 /** Boolean
17 */
18 typedef char bool;
19 #define TRUE ((bool)(-1))
20 #define FALSE ((bool)(0))
21
22 /** MBox pointer
23 */
24 typedef struct
25 {
26 FILE *fp; // File pointer
27 long int lastLine; // Offset of the precedent line (-1 if invalid)
28 long int currentLine; // Offset of the current line
29 long int messageId; // Current message Id
30 long int messageBeginning; // Offset of the beginning of the message (FROM_ line)
31
32 char *line; // Line buffer
33 bool isFrom_; // Is the current line a From_ line ?
34 }
35 MBox;
36
37 /** Open a mbox
38 */
39 MBox *openMBox(char *filename)
40 {
41 FILE *fp;
42 MBox *mbox;
43
44 fp = fopen(filename, "r");
45 if (!fp) {
46 return NULL;
47 }
48
49 mbox = (MBox*)malloc(sizeof(MBox));
50 mbox->fp = fp;
51 mbox->lastLine = -1;
52 mbox->currentLine = 0;
53 mbox->messageId = 0;
54 mbox->messageBeginning = 0;
55 mbox->line = NULL;
56 mbox->isFrom_ = FALSE;
57 return mbox;
58 }
59
60 /** Close a mbox
61 */
62 void closeMBox(MBox *mbox)
63 {
64 if (!mbox) {
65 return;
66 }
67 fclose(mbox->fp);
68 if (mbox->line) {
69 free(mbox->line);
70 }
71 free(mbox);
72 }
73
74 /** Read a line in a file
75 */
76 char *readLine(MBox *mbox)
77 {
78 int length;
79 mbox->lastLine = mbox->currentLine;
80 mbox->currentLine = ftell(mbox->fp);
81 mbox->isFrom_ = FALSE;
82
83 if (!mbox->line) {
84 mbox->line = (char*)malloc(1001);
85 }
86 if (!fgets(mbox->line, 1000, mbox->fp)) {
87 mbox->currentLine = -1;
88 return NULL;
89 }
90 length = ftell(mbox->fp) - mbox->currentLine;
91 if (length > 1000) {
92 length = 1000;
93 }
94 if (length) {
95 while (length >= 0 && (isspace(mbox->line[length]) || mbox->line[length] == '\0')) {
96 length--;
97 }
98 mbox->line[length + 1] = '\0';
99 }
100 mbox->isFrom_ = (strstr(mbox->line, "From ") == mbox->line);
101 if (mbox->isFrom_ && mbox->messageBeginning != mbox->currentLine) {
102 mbox->messageBeginning = mbox->currentLine;
103 mbox->messageId++;
104 }
105 return mbox->line;
106 }
107
108 /** Return to the last line
109 */
110 bool lastLine(MBox *mbox)
111 {
112 if (mbox->lastLine != -1) {
113 fseek(mbox->fp, mbox->lastLine, SEEK_SET);
114 mbox->lastLine = -1;
115 readLine(mbox);
116 return TRUE;
117 }
118 return FALSE;
119 }
120
121 bool readFrom_(MBox *mbox)
122 {
123 if (!mbox->isFrom_) {
124 readLine(mbox);
125 }
126 if (!mbox->isFrom_) {
127 return FALSE;
128 }
129 return TRUE;
130 }
131
132 /** Read a message
133 */
134 void readMessage(MBox *mbox, bool display)
135 {
136 if (!readFrom_(mbox)) {
137 return;
138 }
139 while (readLine(mbox)) {
140 if (mbox->isFrom_) {
141 return;
142 }
143 if (display) {
144 if (strstr(mbox->line, ">From ") == mbox->line) {
145 puts(mbox->line + 1);
146 } else {
147 puts(mbox->line);
148 }
149 }
150 }
151 }
152
153 /** Read the headers of a message
154 */
155 void readHeaders(MBox *mbox, char **headers, int hdrsize)
156 {
157 char *current = NULL;
158 char *pos, *ptr;
159 int size, i;
160
161 if (!readFrom_(mbox)) {
162 return;
163 }
164 printf("%d\n%d\n", mbox->messageId, mbox->messageBeginning);
165 while (readLine(mbox)) {
166 if (mbox->isFrom_ || !strlen(mbox->line)) {
167 break;
168 }
169 if (current && strlen(mbox->line) && isspace(*(mbox->line))) {
170 pos = mbox->line;
171 LTRIM(pos);
172 printf(" %s", pos);
173 } else {
174 if (current) {
175 printf("\n");
176 free(current);
177 current = NULL;
178 }
179 pos = strchr(mbox->line, ':');
180 if (!pos || pos == mbox->line) {
181 continue;
182 }
183 size = pos - mbox->line;
184 for (i = 0 ; i < hdrsize ; i++) {
185 if (strlen(headers[i]) == size && strcasestr(mbox->line, headers[i]) == mbox->line) {
186 current = (char*)malloc(size + 1);
187 strcpy(current, headers[i]);
188 current[size] = '\0';
189 }
190 }
191 if (!current && !hdrsize) {
192 current = (char*)malloc(size + 1);
193 strncpy(current, mbox->line, size);
194 current[size] = '\0';
195 STRTOLOWER(current, ptr);
196 }
197 if (current) {
198 puts(current);
199 pos++;
200 LTRIM(pos);
201 printf("%s", pos);
202 }
203 }
204 }
205 if (current) {
206 printf("\n");
207 free(current);
208 current = NULL;
209 }
210 printf("\n");
211 }
212
213 /** Go back to the beginning of the file
214 */
215 void rewindMBox(MBox *mbox)
216 {
217 fseek(mbox->fp, 0, SEEK_SET);
218 mbox->messageId = 0;
219 mbox->messageBeginning = 0;
220 readLine(mbox);
221 }
222
223 /** Go back to the beginning of the message
224 */
225 bool rewindMessage(MBox *mbox)
226 {
227 if (mbox->isFrom_) {
228 return TRUE;
229 }
230 fseek(mbox->fp, mbox->messageBeginning, SEEK_SET);
231 mbox->currentLine = -1;
232 mbox->lastLine = -1;
233 readLine(mbox);
234 return mbox->isFrom_;
235 }
236
237 /** Move to the given offset
238 */
239 bool goToOffset(MBox *mbox, int offset, int index)
240 {
241 fseek(mbox->fp, offset, SEEK_SET);
242 mbox->currentLine = -1;
243 mbox->lastLine = -1;
244 mbox->messageBeginning = offset;
245 mbox->messageId = index;
246 readLine(mbox);
247 if (!mbox->isFrom_) {
248 return FALSE;
249 }
250 return TRUE;
251 }
252
253 /** Move to the given message number
254 */
255 bool goToMessage(MBox *mbox, int index)
256 {
257 if (mbox->messageId > index) {
258 rewindMBox(mbox);
259 } else if(mbox->messageId == index) {
260 rewindMessage(mbox);
261 return TRUE;
262 } else if (!mbox->isFrom_) {
263 while (!feof(mbox->fp) && !mbox->isFrom_) {
264 readLine(mbox);
265 }
266 if (feof(mbox->fp)) {
267 return FALSE;
268 }
269 }
270 while (mbox->messageId < index && !feof(mbox->fp)) {
271 readMessage(mbox, FALSE);
272 }
273 if (mbox->messageId == index) {
274 return TRUE;
275 }
276 return FALSE;
277 }
278
279
280 /** Display the program help
281 */
282 void help(void)
283 {
284 printf("Usage: mbox-helper [action] [options] -f filename [header1 [header2 ...]]\n"
285 "Actions: only the last action given is applied\n"
286 " -c compute the number of messages. If -p is given, process the file starting à the given offset\n"
287 " -d return the headers of the messages given with the -m option. If no header is given in the\n"
288 " command line options, all the headers are returned. The headers are return with the format:\n"
289 " MSG1_ID\\n\n"
290 " MSG1_OFFSET\\n\n"
291 " MSG1_HEADER1_NAME\\n\n"
292 " MSG1_HEADER1_VALUE\\n\n"
293 " MSG1_HEADER2_NAME\\n\n"
294 " MSG2_HEADER2_VALUE\\n\n"
295 " ...\n"
296 " Messages are separated by a blank line\n"
297 " -b return the body of the message given by -m (only 1 message is returned)\n"
298 "Options:\n"
299 " -m begin[:end] id or range of messages to process\n"
300 " -p id:pos indicate that message `id` begins at offset `pos`\n"
301 " -h print this help\n");
302 }
303
304 /** Display an error message
305 * This function display the giver error, then show the program help and exit the program
306 */
307 void error(char *message)
308 {
309 fprintf(stderr, "Invalid parameters: %s\n", message);
310 help();
311 exit(1);
312 }
313
314 /** Main function
315 */
316 int main(int argc, char *argv[])
317 {
318 int c, i = 0;
319 int fmid = -1, lmid = -1;
320 int pmid = 0, pos = 0;
321 char *filename = NULL;
322 char **headers = NULL;
323 char action;
324 int headerNb = 0;
325 char *endptr;
326 MBox *mbox;
327
328 /* getopt variables */
329 extern char *optarg;
330 extern int optind, optopt;
331
332 while ((c = getopt(argc, argv, ":bcdp:hm:f:")) != -1) {
333 switch (c) {
334 case 'f':
335 filename = optarg;
336 break;
337 case 'm':
338 fmid = strtol(optarg, &endptr, 10);
339 if (endptr == optarg) {
340 error("invalid message id");
341 }
342 if (*endptr != ':') {
343 lmid = fmid;
344 } else {
345 lmid = atoi(endptr + 1);
346 }
347 break;
348 case 'p':
349 if ((endptr = strchr(optarg, ':')) != NULL) {
350 pmid = strtol(optarg, &endptr, 10);
351 if (*endptr != ':') {
352 error("invalid position couple given");
353 }
354 pos = atoi(endptr + 1);
355 } else {
356 error("invalid position given");
357 }
358 break;
359 case 'c': case 'd': case 'b':
360 action = c;
361 break;
362 case 'h':
363 help();
364 return 0;
365 case ':':
366 fprintf(stderr, "Missing argument to -%c\n", optopt);
367 break;
368 case '?':
369 fprintf(stderr, "Unrecognized option: -%c\n", optopt);
370 break;
371 }
372 }
373
374 if (!filename) {
375 error("no file defined");
376 }
377
378 setlocale(LC_ALL, "C");
379
380 headerNb = argc - optind;
381 headers = (argv + optind);
382 for (i = 0 ; i < headerNb ; i++) {
383 STRTOLOWER(headers[i], endptr);
384 }
385
386 mbox = openMBox(filename);
387 if (!mbox) {
388 fprintf(stderr, "can't open file '%s'", filename);
389 }
390 if ((fmid >= pmid || fmid == -1) && pos) {
391 if (!goToOffset(mbox, pos, pmid)) {
392 fprintf(stderr, "Offset %d do not match with a message beginning\n", pos);
393 rewindMBox(mbox);
394 }
395 }
396 switch (action) {
397 case 'b':
398 if (fmid == -1) {
399 fprintf(stderr, "you have to define a message number");
400 break;
401 }
402 goToMessage(mbox, fmid);
403 readMessage(mbox, TRUE);
404 break;
405 case 'c':
406 while (!feof(mbox->fp)) {
407 readLine(mbox);
408 }
409 printf("%d\n", mbox->messageId + 1);
410 break;
411 case 'd':
412 if (fmid == -1) {
413 fprintf(stderr, "you have to define a message number");
414 break;
415 }
416 for (i = fmid ; i <= lmid ; i++) {
417 goToMessage(mbox, i);
418 readHeaders(mbox, headers, headerNb);
419 }
420 break;
421 }
422 closeMBox(mbox);
423
424 return 0;
425 }