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