19fc7e1d |
1 | /** Read an mbox |
2 | */ |
3 | |
02ad273d |
4 | #define _GNU_SOURCE |
19fc7e1d |
5 | #include <unistd.h> |
6 | #include <stdio.h> |
7 | #include <stdlib.h> |
8 | #include <string.h> |
9 | #include <ctype.h> |
10 | #include <locale.h> |
21090901 |
11 | #include <stdbool.h> |
12 | #include <getopt.h> |
19fc7e1d |
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 | |
19fc7e1d |
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 | */ |
21090901 |
36 | static MBox *openMBox(char *filename) |
19fc7e1d |
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; |
21090901 |
53 | mbox->isFrom_ = false; |
19fc7e1d |
54 | return mbox; |
55 | } |
56 | |
57 | /** Close a mbox |
58 | */ |
21090901 |
59 | static void closeMBox(MBox *mbox) |
19fc7e1d |
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 | */ |
21090901 |
73 | static char *readLine(MBox *mbox) |
19fc7e1d |
74 | { |
75 | int length; |
76 | mbox->lastLine = mbox->currentLine; |
77 | mbox->currentLine = ftell(mbox->fp); |
21090901 |
78 | mbox->isFrom_ = false; |
19fc7e1d |
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 | |
21090901 |
105 | #if 0 /* unused right now */ |
19fc7e1d |
106 | /** Return to the last line |
107 | */ |
21090901 |
108 | static bool lastLine(MBox *mbox) |
19fc7e1d |
109 | { |
110 | if (mbox->lastLine != -1) { |
111 | fseek(mbox->fp, mbox->lastLine, SEEK_SET); |
112 | mbox->lastLine = -1; |
113 | readLine(mbox); |
21090901 |
114 | return true; |
19fc7e1d |
115 | } |
21090901 |
116 | return false; |
19fc7e1d |
117 | } |
21090901 |
118 | #endif |
19fc7e1d |
119 | |
21090901 |
120 | static bool readFrom_(MBox *mbox) |
19fc7e1d |
121 | { |
122 | if (!mbox->isFrom_) { |
123 | readLine(mbox); |
124 | } |
21090901 |
125 | return !!mbox->isFrom_; |
19fc7e1d |
126 | } |
127 | |
128 | /** Read a message |
129 | */ |
21090901 |
130 | static void readMessage(MBox *mbox, bool display) |
19fc7e1d |
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 | */ |
21090901 |
151 | static void readHeaders(MBox *mbox, char **headers, int hdrsize) |
19fc7e1d |
152 | { |
153 | char *current = NULL; |
154 | char *pos, *ptr; |
155 | int size, i; |
156 | |
157 | if (!readFrom_(mbox)) { |
158 | return; |
159 | } |
9dc9bd6d |
160 | printf("%d\n%d\n", (int)mbox->messageId, (int)mbox->messageBeginning); |
19fc7e1d |
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++) { |
21090901 |
181 | if ((int)strlen(headers[i]) == size && strcasestr(mbox->line, headers[i]) == mbox->line) { |
19fc7e1d |
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 | */ |
21090901 |
211 | static void rewindMBox(MBox *mbox) |
19fc7e1d |
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 | */ |
21090901 |
221 | static bool rewindMessage(MBox *mbox) |
19fc7e1d |
222 | { |
223 | if (mbox->isFrom_) { |
21090901 |
224 | return true; |
19fc7e1d |
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 | */ |
21090901 |
235 | static bool goToOffset(MBox *mbox, int offset, int idx) |
19fc7e1d |
236 | { |
237 | fseek(mbox->fp, offset, SEEK_SET); |
238 | mbox->currentLine = -1; |
239 | mbox->lastLine = -1; |
240 | mbox->messageBeginning = offset; |
21090901 |
241 | mbox->messageId = idx; |
19fc7e1d |
242 | readLine(mbox); |
243 | if (!mbox->isFrom_) { |
21090901 |
244 | return false; |
19fc7e1d |
245 | } |
21090901 |
246 | return true; |
19fc7e1d |
247 | } |
248 | |
249 | /** Move to the given message number |
250 | */ |
21090901 |
251 | static bool goToMessage(MBox *mbox, int idx) |
19fc7e1d |
252 | { |
21090901 |
253 | if (mbox->messageId > idx) { |
19fc7e1d |
254 | rewindMBox(mbox); |
21090901 |
255 | } else if(mbox->messageId == idx) { |
19fc7e1d |
256 | rewindMessage(mbox); |
21090901 |
257 | return true; |
19fc7e1d |
258 | } else if (!mbox->isFrom_) { |
259 | while (!feof(mbox->fp) && !mbox->isFrom_) { |
260 | readLine(mbox); |
261 | } |
262 | if (feof(mbox->fp)) { |
21090901 |
263 | return false; |
19fc7e1d |
264 | } |
265 | } |
21090901 |
266 | while (mbox->messageId < idx && !feof(mbox->fp)) { |
267 | readMessage(mbox, false); |
19fc7e1d |
268 | } |
21090901 |
269 | if (mbox->messageId == idx) { |
270 | return true; |
19fc7e1d |
271 | } |
21090901 |
272 | return false; |
19fc7e1d |
273 | } |
274 | |
275 | |
276 | /** Display the program help |
277 | */ |
21090901 |
278 | static void help(void) |
19fc7e1d |
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 | */ |
21090901 |
303 | static void error(const char *message) |
19fc7e1d |
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; |
9dc9bd6d |
319 | char action = 0; |
19fc7e1d |
320 | int headerNb = 0; |
321 | char *endptr; |
322 | MBox *mbox; |
19fc7e1d |
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 != ':') { |
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 != ':') { |
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); |
0eeb527e |
381 | return 1; |
19fc7e1d |
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); |
21090901 |
396 | readMessage(mbox, true); |
19fc7e1d |
397 | break; |
398 | case 'c': |
399 | while (!feof(mbox->fp)) { |
400 | readLine(mbox); |
401 | } |
9dc9bd6d |
402 | printf("%d\n", (int)(mbox->messageId + 1)); |
19fc7e1d |
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 | } |