| 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 | } |