| 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 != ':') { |
| 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); |
| 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 | } |