Remove non significant spaces
[banana.git] / mbox-helper / mbox-helper.c
CommitLineData
dff768f0 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********************************************************************************/
19fc7e1d 8
02ad273d 9#define _GNU_SOURCE
19fc7e1d 10#include <unistd.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <string.h>
14#include <ctype.h>
15#include <locale.h>
21090901 16#include <stdbool.h>
17#include <getopt.h>
19fc7e1d 18
19/** Macros
20 */
91f07bb6 21#define LTRIM(pos) while (isspace(*pos)) { ++pos; }
22#define STRTOLOWER(str, ptr) for (ptr = str ; *ptr ; ++ptr) { *ptr = tolower(*ptr); }
19fc7e1d 23
19fc7e1d 24/** MBox pointer
25 */
26typedef struct
27{
dff768f0 28 FILE *fp; // File pointer
19fc7e1d 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}
37MBox;
38
39/** Open a mbox
dff768f0 40 * @param filename char* Path to the file to open
41 * @return NULL on error, a well initialized MBox structure pointer on success
19fc7e1d 42 */
21090901 43static MBox *openMBox(char *filename)
19fc7e1d 44{
45 FILE *fp;
46 MBox *mbox;
47
91f07bb6 48 fp = fopen(filename, "r");
19fc7e1d 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;
21090901 60 mbox->isFrom_ = false;
19fc7e1d 61 return mbox;
62}
63
64/** Close a mbox
65 */
21090901 66static void closeMBox(MBox *mbox)
19fc7e1d 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
dff768f0 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
19fc7e1d 84 */
21090901 85static char *readLine(MBox *mbox)
19fc7e1d 86{
87 int length;
88 mbox->lastLine = mbox->currentLine;
89 mbox->currentLine = ftell(mbox->fp);
21090901 90 mbox->isFrom_ = false;
91f07bb6 91
19fc7e1d 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
dff768f0 117/** Read a From_ line from the mbox
118 * the From_ line MUST be the current or the next one
19fc7e1d 119 */
21090901 120static 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
dff768f0 129 * The message is not stored or returned, just skipped.
130 * If display is true, the message is printed on stdio
19fc7e1d 131 */
21090901 132static void readMessage(MBox *mbox, bool display)
19fc7e1d 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
dff768f0 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
19fc7e1d 158 */
21090901 159static void readHeaders(MBox *mbox, char **headers, int hdrsize)
19fc7e1d 160{
161 char *current = NULL;
162 char *pos, *ptr;
163 int size, i;
91f07bb6 164
19fc7e1d 165 if (!readFrom_(mbox)) {
166 return;
167 }
9dc9bd6d 168 printf("%d\n%d\n", (int)mbox->messageId, (int)mbox->messageBeginning);
19fc7e1d 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;
91f07bb6 188 for (i = 0 ; i < hdrsize ; ++i) {
21090901 189 if ((int)strlen(headers[i]) == size && strcasestr(mbox->line, headers[i]) == mbox->line) {
19fc7e1d 190 current = (char*)malloc(size + 1);
191 strcpy(current, headers[i]);
91f07bb6 192 current[size] = '\0';
19fc7e1d 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 */
21090901 219static void rewindMBox(MBox *mbox)
19fc7e1d 220{
221 fseek(mbox->fp, 0, SEEK_SET);
222 mbox->messageId = 0;
223 mbox->messageBeginning = 0;
224 readLine(mbox);
225}
226
dff768f0 227/** Go back to the beginning of the current message
228 * @return true if the beginning of a message has been reached
19fc7e1d 229 */
21090901 230static bool rewindMessage(MBox *mbox)
19fc7e1d 231{
232 if (mbox->isFrom_) {
21090901 233 return true;
19fc7e1d 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
dff768f0 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
19fc7e1d 247 */
21090901 248static bool goToOffset(MBox *mbox, int offset, int idx)
19fc7e1d 249{
250 fseek(mbox->fp, offset, SEEK_SET);
251 mbox->currentLine = -1;
252 mbox->lastLine = -1;
253 mbox->messageBeginning = offset;
21090901 254 mbox->messageId = idx;
19fc7e1d 255 readLine(mbox);
256 if (!mbox->isFrom_) {
21090901 257 return false;
19fc7e1d 258 }
21090901 259 return true;
19fc7e1d 260}
261
262/** Move to the given message number
dff768f0 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
19fc7e1d 266 */
21090901 267static bool goToMessage(MBox *mbox, int idx)
19fc7e1d 268{
21090901 269 if (mbox->messageId > idx) {
91f07bb6 270 rewindMBox(mbox);
21090901 271 } else if(mbox->messageId == idx) {
19fc7e1d 272 rewindMessage(mbox);
21090901 273 return true;
19fc7e1d 274 } else if (!mbox->isFrom_) {
275 while (!feof(mbox->fp) && !mbox->isFrom_) {
276 readLine(mbox);
277 }
278 if (feof(mbox->fp)) {
21090901 279 return false;
19fc7e1d 280 }
281 }
21090901 282 while (mbox->messageId < idx && !feof(mbox->fp)) {
283 readMessage(mbox, false);
19fc7e1d 284 }
21090901 285 if (mbox->messageId == idx) {
286 return true;
19fc7e1d 287 }
21090901 288 return false;
19fc7e1d 289}
290
291
dff768f0 292/** Display the program usage help
19fc7e1d 293 */
21090901 294static void help(void)
19fc7e1d 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
dff768f0 318 * The memory must be cleared before calling this function
19fc7e1d 319 */
21090901 320static void error(const char *message)
19fc7e1d 321{
322 fprintf(stderr, "Invalid parameters: %s\n", message);
323 help();
324 exit(1);
325}
326
327/** Main function
328 */
329int 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;
9dc9bd6d 336 char action = 0;
19fc7e1d 337 int headerNb = 0;
338 char *endptr;
339 MBox *mbox;
19fc7e1d 340
dff768f0 341 // Parse command line
19fc7e1d 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 }
6c6b7002 352 if (*endptr != ':' || !*(endptr+1)) {
19fc7e1d 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);
6c6b7002 361 if (*endptr != ':' || !*(endptr+1)) {
19fc7e1d 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 }
dff768f0 383
384 // Check command line arguments consistence
19fc7e1d 385 if (!filename) {
386 error("no file defined");
387 }
388
389 setlocale(LC_ALL, "C");
91f07bb6 390
19fc7e1d 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) {
dc5f77ad 399 fprintf(stderr, "can't open file '%s'\n", filename);
0eeb527e 400 return 1;
19fc7e1d 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);
91f07bb6 406 }
19fc7e1d 407 }
dff768f0 408
409 // Do requested stuff
19fc7e1d 410 switch (action) {
411 case 'b':
412 if (fmid == -1) {
dc5f77ad 413 fprintf(stderr, "you have to define a message number\n");
19fc7e1d 414 break;
415 }
416 goToMessage(mbox, fmid);
21090901 417 readMessage(mbox, true);
19fc7e1d 418 break;
419 case 'c':
420 while (!feof(mbox->fp)) {
421 readLine(mbox);
422 }
9dc9bd6d 423 printf("%d\n", (int)(mbox->messageId + 1));
19fc7e1d 424 break;
425 case 'd':
426 if (fmid == -1) {
dc5f77ad 427 fprintf(stderr, "you have to define a message number\n");
19fc7e1d 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}