19fc7e1d |
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 | } |
9dc9bd6d |
164 | printf("%d\n%d\n", (int)mbox->messageId, (int)mbox->messageBeginning); |
19fc7e1d |
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; |
9dc9bd6d |
323 | char action = 0; |
19fc7e1d |
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 | } |
9dc9bd6d |
409 | printf("%d\n", (int)(mbox->messageId + 1)); |
19fc7e1d |
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 | } |