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