1 /****************************************************************************** 2 * $Id$ 3 * 4 * MboxToBeMail is a utility program that converts Unix mailbox (mbox) files 5 * (the kind that Pine uses) into e-mail files for use with BeOS. It also 6 * handles news files from rn and trn, which have messages very similar to mail 7 * messages but with a different separator line. The input files store 8 * multiple mail messages in text format separated by "From ..." lines or 9 * "Article ..." lines. The output is a bunch of separate files, each one with 10 * one message plus BeOS BFS attributes describing that message. For 11 * convenience, all the messages that were from one file are put in a specified 12 * directory. 13 * 14 * Command line driven. Version 1.7 and up (which have support for 15 * international character sets (as requested by "MFC" Zahar from Russia) and 16 * the Thread attribute) require the libmail.so that comes with the Mail Daemon 17 * Replacement (MDR) version 2.2.6 or later (http://www.bebits.com/app/2289 or 18 * http://sourceforge.net/projects/bemaildaemon/). Use version 1.6 (get it 19 * from the version history pages on BeBits) if you don't want to install MDR. 20 * 21 * $Log: MailboxToBeMail.cpp,v $ (manually updated now that SVN is used) 22 * r13960 | agmsmith | 2005-08-13 22:10:49 -0400 (Sat, 13 Aug 2005) | 3 lines 23 * More file movement for the BeMail utilities... Content updates later to 24 * avoid confusing svn. 25 * 26 * r13955 | agmsmith | 2005-08-13 19:43:41 -0400 (Sat, 13 Aug 2005) | 5 lines 27 * Half way through adding some more BeMail related utilities - they use 28 * libmail.so which isn't backwards compatibile so they need recompiling for 29 * Haiku - thus better to include them here. Also want spam levels of 1E-6 to 30 * be visible for genuine messages. 31 * 32 * Revision 1.9 2003/12/31 00:37:46 agmsmith 33 * Use the MDR date parsing routine rather than parsedate, so it 34 * can handle newer date formats. 35 * 36 * Revision 1.8 2003/12/28 19:03:57 agmsmith 37 * Added an option to save or not save the separator text line. 38 * 39 * Revision 1.7 2003/12/28 04:23:21 agmsmith 40 * Now uses the Mail Daemon Replacement library to parse the headers, 41 * which means they now handle foreign character sets (converted 42 * automatically to UTF-8), so Subject and To are correct when they 43 * are used as part of the file name. 44 * 45 * Revision 1.6 2002/01/14 20:23:21 agmsmith 46 * Include the detection line with the message header, just for completeness. 47 * 48 * Revision 1.5 2002/01/14 03:21:43 agmsmith 49 * Added processing of UseNet articles, and allowed an older date format. 50 * 51 * Revision 1.4 2002/01/14 00:06:59 agmsmith 52 * Don't allow slashes in file names. 53 * 54 * Revision 1.3 2002/01/13 23:33:26 agmsmith 55 * First seemingly working version. 56 * 57 * Revision 1.2 2002/01/13 17:57:26 agmsmith 58 * Can now pick out separate messages from the inbox. 59 * 60 * Revision 1.1 2002/01/13 15:25:33 agmsmith 61 * Initial revision 62 */ 63 64 /* BeOS headers. */ 65 66 #include <Application.h> 67 #include <E-mail.h> 68 #include <StorageKit.h> 69 #include <SupportKit.h> 70 71 /* Posix headers. */ 72 73 #include <string.h> 74 #include <stdio.h> 75 #include <ctype.h> 76 #include <errno.h> 77 78 /* MDR headers (get the MDR source and add the MDR include/public and 79 include/numail directories to the project path settings). Or just use 80 the Haiku build process which is already set up to include the right ones. */ 81 82 #include <MailMessage.h> 83 #include <mail_util.h> 84 85 86 87 /****************************************************************************** 88 * Globals 89 */ 90 91 char InputPathName [B_PATH_NAME_LENGTH]; 92 FILE *InputFile; 93 BDirectory OutputDir; 94 95 96 typedef enum StandardHeaderEnum 97 { 98 STD_HDR_DATE = 0, /* The Date: field. First one since it is numeric. */ 99 STD_HDR_FROM, /* The whole From: field, including quotes and address. */ 100 STD_HDR_TO, /* All the stuff in the To: field. */ 101 STD_HDR_CC, /* All the CC: field (originally means carbon copy). */ 102 STD_HDR_REPLY, /* Things in the reply-to: field. */ 103 STD_HDR_SUBJECT, /* The Subject: field. */ 104 STD_HDR_PRIORITY, /* The Priority: and related fields, usually "Normal". */ 105 STD_HDR_STATUS, /* The BeOS mail Read / New status text attribute. */ 106 STD_HDR_THREAD, /* The subject simplified. */ 107 STD_HDR_NAME, /* The From address simplified into a plain name. */ 108 STD_HDR_MAX 109 } StandardHeaderCodes; 110 111 const char *g_StandardAttributeNames [STD_HDR_MAX] = 112 { 113 B_MAIL_ATTR_WHEN, 114 B_MAIL_ATTR_FROM, 115 B_MAIL_ATTR_TO, 116 B_MAIL_ATTR_CC, 117 B_MAIL_ATTR_REPLY, 118 B_MAIL_ATTR_SUBJECT, 119 B_MAIL_ATTR_PRIORITY, 120 B_MAIL_ATTR_STATUS, 121 B_MAIL_ATTR_THREAD, 122 B_MAIL_ATTR_NAME 123 }; 124 125 126 127 /****************************************************************************** 128 * Global utility function to display an error message and return. The message 129 * part describes the error, and if ErrorNumber is non-zero, gets the string 130 * ", error code $X (standard description)." appended to it. If the message 131 * is NULL then it gets defaulted to "Something went wrong". 132 */ 133 134 static void DisplayErrorMessage ( 135 const char *MessageString = NULL, 136 int ErrorNumber = 0, 137 const char *TitleString = NULL) 138 { 139 char ErrorBuffer [B_PATH_NAME_LENGTH + 80 /* error message */ + 80]; 140 141 if (TitleString == NULL) 142 TitleString = "Error Message:"; 143 144 if (MessageString == NULL) 145 { 146 if (ErrorNumber == 0) 147 MessageString = "No error, no message, why bother?"; 148 else 149 MessageString = "Something went wrong"; 150 } 151 152 if (ErrorNumber != 0) 153 { 154 sprintf (ErrorBuffer, "%s, error code $%X/%d (%s) has occured.", 155 MessageString, ErrorNumber, ErrorNumber, strerror (ErrorNumber)); 156 MessageString = ErrorBuffer; 157 } 158 159 fputs (TitleString, stderr); 160 fputc ('\n', stderr); 161 fputs (MessageString, stderr); 162 fputc ('\n', stderr); 163 } 164 165 166 167 /****************************************************************************** 168 * Determine if a line of text is the start of another message. Pine mailbox 169 * files have messages that start with a line that could say something like 170 * "From agmsmith@achilles.net Fri Oct 31 21:19:36 EST 1997" or maybe something 171 * like "From POPmail Mon Oct 20 21:12:36 1997" or in a more modern format, 172 * "From agmsmith@achilles.net Tue Sep 4 09:04:11 2001 -0400". I generalise it 173 * to "From blah Day MMM NN XX:XX:XX TZONE1 YYYY TZONE2". Blah is an e-mail 174 * address you can ignore (just treat it as a word separated by spaces). Day 175 * is a 3 letter day of the week. MMM is a 3 letter month name. NN is the two 176 * digit day of the week, has a leading space if the day is less than 10. 177 * XX:XX:XX is the time, the X's are digits. TZONE1 is the old style optional 178 * time zone of 3 capital letters. YYYY is the four digit year. TZONE2 is the 179 * optional modern time zone info, a plus or minus sign and 4 digits. Returns 180 * true if the line of text (ended with a NUL byte, no line feed or carriage 181 * returns at the end) is the start of a message. 182 */ 183 184 bool IsStartOfMailMessage (char *LineString) 185 { 186 char *StringPntr; 187 188 /* It starts with "From " */ 189 190 if (memcmp ("From ", LineString, 5) != 0) 191 return false; 192 StringPntr = LineString + 4; 193 while (*StringPntr == ' ') 194 StringPntr++; 195 196 /* Skip over the e-mail address (or stop at the end of string). */ 197 198 while (*StringPntr != ' ' && *StringPntr != 0) 199 StringPntr++; 200 while (*StringPntr == ' ') 201 StringPntr++; 202 203 /* Look for the 3 letter day of the week. */ 204 205 if (memcmp (StringPntr, "Mon", 3) != 0 && 206 memcmp (StringPntr, "Tue", 3) != 0 && 207 memcmp (StringPntr, "Wed", 3) != 0 && 208 memcmp (StringPntr, "Thu", 3) != 0 && 209 memcmp (StringPntr, "Fri", 3) != 0 && 210 memcmp (StringPntr, "Sat", 3) != 0 && 211 memcmp (StringPntr, "Sun", 3) != 0) 212 { 213 printf ("False alarm, not a valid day of the week in \"%s\".\n", 214 LineString); 215 return false; 216 } 217 StringPntr += 3; 218 while (*StringPntr == ' ') 219 StringPntr++; 220 221 /* Look for the 3 letter month code. */ 222 223 if (memcmp (StringPntr, "Jan", 3) != 0 && 224 memcmp (StringPntr, "Feb", 3) != 0 && 225 memcmp (StringPntr, "Mar", 3) != 0 && 226 memcmp (StringPntr, "Apr", 3) != 0 && 227 memcmp (StringPntr, "May", 3) != 0 && 228 memcmp (StringPntr, "Jun", 3) != 0 && 229 memcmp (StringPntr, "Jul", 3) != 0 && 230 memcmp (StringPntr, "Aug", 3) != 0 && 231 memcmp (StringPntr, "Sep", 3) != 0 && 232 memcmp (StringPntr, "Oct", 3) != 0 && 233 memcmp (StringPntr, "Nov", 3) != 0 && 234 memcmp (StringPntr, "Dec", 3) != 0) 235 { 236 printf ("False alarm, not a valid month name in \"%s\".\n", 237 LineString); 238 return false; 239 } 240 StringPntr += 3; 241 while (*StringPntr == ' ') 242 StringPntr++; 243 244 /* Skip the day of the month. Require at least one digit. */ 245 246 if (*StringPntr < '0' || *StringPntr > '9') 247 { 248 printf ("False alarm, not a valid day of the month number in \"%s\".\n", 249 LineString); 250 return false; 251 } 252 while (*StringPntr >= '0' && *StringPntr <= '9') 253 StringPntr++; 254 while (*StringPntr == ' ') 255 StringPntr++; 256 257 /* Check the time. Look for the sequence 258 digit-digit-colon-digit-digit-colon-digit-digit. */ 259 260 if (StringPntr[0] < '0' || StringPntr[0] > '9' || 261 StringPntr[1] < '0' || StringPntr[1] > '9' || 262 StringPntr[2] != ':' || 263 StringPntr[3] < '0' || StringPntr[3] > '9' || 264 StringPntr[4] < '0' || StringPntr[4] > '9' || 265 StringPntr[5] != ':' || 266 StringPntr[6] < '0' || StringPntr[6] > '9' || 267 StringPntr[7] < '0' || StringPntr[7] > '9') 268 { 269 printf ("False alarm, not a valid time value in \"%s\".\n", 270 LineString); 271 return false; 272 } 273 StringPntr += 8; 274 while (*StringPntr == ' ') 275 StringPntr++; 276 277 /* Look for the optional antique 3 capital letter time zone and skip it. */ 278 279 if (StringPntr[0] >= 'A' && StringPntr[0] <= 'Z' && 280 StringPntr[1] >= 'A' && StringPntr[1] <= 'Z' && 281 StringPntr[2] >= 'A' && StringPntr[2] <= 'Z') 282 { 283 StringPntr += 3; 284 while (*StringPntr == ' ') 285 StringPntr++; 286 } 287 288 /* Look for the 4 digit year. */ 289 290 if (StringPntr[0] < '0' || StringPntr[0] > '9' || 291 StringPntr[1] < '0' || StringPntr[1] > '9' || 292 StringPntr[2] < '0' || StringPntr[2] > '9' || 293 StringPntr[3] < '0' || StringPntr[3] > '9') 294 { 295 printf ("False alarm, not a valid 4 digit year in \"%s\".\n", 296 LineString); 297 return false; 298 } 299 StringPntr += 4; 300 while (*StringPntr == ' ') 301 StringPntr++; 302 303 /* Look for the optional modern time zone and skip over it if present. */ 304 305 if ((StringPntr[0] == '+' || StringPntr[0] == '-') && 306 StringPntr[1] >= '0' && StringPntr[1] <= '9' && 307 StringPntr[2] >= '0' && StringPntr[2] <= '9' && 308 StringPntr[3] >= '0' && StringPntr[3] <= '9' && 309 StringPntr[4] >= '0' && StringPntr[4] <= '9') 310 { 311 StringPntr += 5; 312 while (*StringPntr == ' ') 313 StringPntr++; 314 } 315 316 /* Look for end of string. */ 317 318 if (*StringPntr != 0) 319 { 320 printf ("False alarm, extra stuff after the year/time zone in \"%s\".\n", 321 LineString); 322 return false; 323 } 324 325 return true; 326 } 327 328 329 330 /****************************************************************************** 331 * Determine if a line of text is the start of a news article. TRN and RN news 332 * article save files have messages that start with a line that looks like 333 * "Article 11721 of rec.games.video.3do:". Returns true if the line of text 334 * (ended with a NUL byte, no line feed or carriage returns at the end) is the 335 * start of an article. 336 */ 337 338 bool IsStartOfUsenetArticle (char *LineString) 339 { 340 char *StringPntr; 341 342 /* It starts with "Article " */ 343 344 if (memcmp ("Article ", LineString, 8) != 0) 345 return false; 346 StringPntr = LineString + 7; 347 while (*StringPntr == ' ') 348 StringPntr++; 349 350 /* Skip the article number. Require at least one digit. */ 351 352 if (*StringPntr < '0' || *StringPntr > '9') 353 { 354 printf ("False alarm, not a valid article number in \"%s\".\n", 355 LineString); 356 return false; 357 } 358 while (*StringPntr >= '0' && *StringPntr <= '9') 359 StringPntr++; 360 while (*StringPntr == ' ') 361 StringPntr++; 362 363 /* Now it should have "of " */ 364 365 if (memcmp ("of ", StringPntr, 3) != 0) 366 { 367 printf ("False alarm, article line \"of\" misssing in \"%s\".\n", 368 LineString); 369 return false; 370 } 371 StringPntr += 2; 372 while (*StringPntr == ' ') 373 StringPntr++; 374 375 /* Skip over the newsgroup name (no spaces) to the colon. */ 376 377 while (*StringPntr != ':' && *StringPntr != ' ' && *StringPntr != 0) 378 StringPntr++; 379 380 if (StringPntr[0] != ':' || StringPntr[1] != 0) 381 { 382 printf ("False alarm, article doesn't end with a colon in \"%s\".\n", 383 LineString); 384 return false; 385 } 386 387 return true; 388 } 389 390 391 392 /****************************************************************************** 393 * Saves the message text to a file in the output directory. The file name is 394 * derived from the message headers. Returns zero if successful, a negative 395 * error code if an error occured. 396 */ 397 398 status_t SaveMessage (BString &MessageText) 399 { 400 time_t DateInSeconds; 401 status_t ErrorCode; 402 BString FileName; 403 BString HeaderValues [STD_HDR_MAX]; 404 int i; 405 int Length; 406 BEmailMessage MailMessage; 407 BFile OutputFile; 408 char TempString [80]; 409 struct tm TimeFields; 410 BString UniqueFileName; 411 int32 Uniquer; 412 413 /* Remove blank lines from the end of the message (a pet peeve of mine), but 414 end the message with a single new line to avoid annoying text editors that 415 like to have it. */ 416 417 i = MessageText.Length (); 418 while (i > 0 && (MessageText[i-1] == '\n' || MessageText[i-1] == '\r')) 419 i--; 420 MessageText.Truncate (i); 421 MessageText.Append ("\r\n"); 422 423 /* Make a pretend file to hold the message, so the MDR library can use it. */ 424 425 BMemoryIO FakeFile (MessageText.String (), MessageText.Length ()); 426 427 /* Hand the message text off to the MDR library, which will parse it, extract 428 the subject, sender's name, and other attributes, taking into account the 429 character set headers. */ 430 431 ErrorCode = MailMessage.SetToRFC822 (&FakeFile, 432 MessageText.Length (), false /* parse_now - decodes message body */); 433 if (ErrorCode != B_OK) 434 { 435 DisplayErrorMessage ("Mail library was unable to process a mail " 436 "message for some reason", ErrorCode); 437 return ErrorCode; 438 } 439 440 /* Get the values for the standard attributes. NULL if missing. */ 441 442 HeaderValues [STD_HDR_TO] = MailMessage.To (); 443 HeaderValues [STD_HDR_FROM] = MailMessage.From (); 444 HeaderValues [STD_HDR_CC] = MailMessage.CC (); 445 HeaderValues [STD_HDR_DATE] = MailMessage.Date (); 446 HeaderValues [STD_HDR_REPLY] = MailMessage.ReplyTo (); 447 HeaderValues [STD_HDR_SUBJECT] = MailMessage.Subject (); 448 HeaderValues [STD_HDR_STATUS] = "Read"; 449 if (MailMessage.Priority () != 3 /* Normal */) 450 { 451 sprintf (TempString, "%d", MailMessage.Priority ()); 452 HeaderValues [STD_HDR_PRIORITY] = TempString; 453 } 454 455 HeaderValues[STD_HDR_THREAD] = HeaderValues[STD_HDR_SUBJECT]; 456 SubjectToThread (HeaderValues[STD_HDR_THREAD]); 457 if (HeaderValues[STD_HDR_THREAD].Length() <= 0) 458 HeaderValues[STD_HDR_THREAD] = "No Subject"; 459 460 HeaderValues[STD_HDR_NAME] = HeaderValues[STD_HDR_FROM]; 461 extract_address_name (HeaderValues[STD_HDR_NAME]); 462 463 // Generate a file name for the incoming message. 464 465 FileName = HeaderValues [STD_HDR_THREAD]; 466 if (FileName[0] == '.') 467 FileName.Prepend ("_"); // Avoid hidden files, starting with a dot. 468 469 // Convert the date into a year-month-day fixed digit width format, so that 470 // sorting by file name will give all the messages with the same subject in 471 // order of date. 472 473 DateInSeconds = 474 ParseDateWithTimeZone (HeaderValues[STD_HDR_DATE].String()); 475 if (DateInSeconds == -1) 476 DateInSeconds = 0; /* Set it to the earliest time if date isn't known. */ 477 478 localtime_r (&DateInSeconds, &TimeFields); 479 sprintf (TempString, "%04d%02d%02d%02d%02d%02d", 480 TimeFields.tm_year + 1900, 481 TimeFields.tm_mon + 1, 482 TimeFields.tm_mday, 483 TimeFields.tm_hour, 484 TimeFields.tm_min, 485 TimeFields.tm_sec); 486 FileName << " " << TempString << " " << HeaderValues[STD_HDR_NAME]; 487 FileName.Truncate (240); // reserve space for the uniquer 488 489 // Get rid of annoying characters which are hard to use in the shell. 490 FileName.ReplaceAll('/','_'); 491 FileName.ReplaceAll('\'','_'); 492 FileName.ReplaceAll('"','_'); 493 FileName.ReplaceAll('!','_'); 494 FileName.ReplaceAll('<','_'); 495 FileName.ReplaceAll('>','_'); 496 while (FileName.FindFirst(" ") >= 0) // Remove multiple spaces. 497 FileName.Replace(" " /* Old */, " " /* New */, 1024 /* Count */); 498 499 Uniquer = 0; 500 UniqueFileName = FileName; 501 while (true) 502 { 503 ErrorCode = OutputFile.SetTo (&OutputDir, 504 const_cast<const char *> (UniqueFileName.String ()), 505 B_READ_WRITE | B_CREATE_FILE | B_FAIL_IF_EXISTS); 506 if (ErrorCode == B_OK) 507 break; 508 if (ErrorCode != B_FILE_EXISTS) 509 { 510 UniqueFileName.Prepend ("Unable to create file \""); 511 UniqueFileName.Append ("\" for writing a message to"); 512 DisplayErrorMessage (UniqueFileName.String (), ErrorCode); 513 return ErrorCode; 514 } 515 Uniquer++; 516 UniqueFileName = FileName; 517 UniqueFileName << " " << Uniquer; 518 } 519 520 /* Write the message contents to the file, use the unchanged original one. */ 521 522 ErrorCode = OutputFile.Write (MessageText.String (), MessageText.Length ()); 523 if (ErrorCode < 0) 524 { 525 UniqueFileName.Prepend ("Error while writing file \""); 526 UniqueFileName.Append ("\""); 527 DisplayErrorMessage (UniqueFileName.String (), ErrorCode); 528 return ErrorCode; 529 } 530 531 /* Attach the attributes to the file. Save the MIME type first, otherwise 532 the live queries don't pick up the new file. Theoretically it would be 533 better to do it last so that other programs don't start reading the message 534 before the other attributes are set. */ 535 536 OutputFile.WriteAttr ("BEOS:TYPE", B_MIME_STRING_TYPE, 0, 537 "text/x-email", 13); 538 539 OutputFile.WriteAttr (g_StandardAttributeNames[STD_HDR_DATE], 540 B_TIME_TYPE, 0, &DateInSeconds, sizeof (DateInSeconds)); 541 542 /* Write out all the string based attributes. */ 543 544 for (i = 1 /* The date was zero */; i < STD_HDR_MAX; i++) 545 { 546 if ((Length = HeaderValues[i].Length()) > 0) 547 OutputFile.WriteAttr (g_StandardAttributeNames[i], B_STRING_TYPE, 0, 548 HeaderValues[i].String(), Length + 1); 549 } 550 551 return 0; 552 } 553 554 555 556 /****************************************************************************** 557 * Finally, the main program which drives it all. 558 */ 559 560 int main (int argc, char** argv) 561 { 562 char ErrorMessage [B_PATH_NAME_LENGTH + 80]; 563 bool HaveOldMessage = false; 564 int MessagesDoneCount = 0; 565 BString MessageText; 566 BApplication MyApp ("application/x-vnd.agmsmith.mboxtobemail"); 567 int NextArgIndex; 568 char OutputDirectoryPathName [B_PATH_NAME_LENGTH]; 569 status_t ReturnCode = -1; 570 bool SaveSeparatorLine = false; 571 char *StringPntr; 572 char TempString [102400]; 573 574 if (argc <= 1) 575 { 576 printf ("%s is a utility for converting Pine e-mail\n", 577 argv[0]); 578 printf ("files (mbox files) to BeOS e-mail files with attributes. It\n"); 579 printf ("could well work with other Unix style mailbox files, and\n"); 580 printf ("saved Usenet article files. Each message in the input\n"); 581 printf ("mailbox is converted into a separate file. You can\n"); 582 printf ("optionally specify a directory (will be created if needed) to\n"); 583 printf ("put all the output files in, otherwise it scatters them into\n"); 584 printf ("the current directory. The -s option makes it leave in the\n"); 585 printf ("separator text line at the top of each message, the default\n"); 586 printf ("is to lose it.\n\n"); 587 printf ("Usage:\n\n"); 588 printf ("mboxtobemail [-s] InputFile [OutputDirectory]\n\n"); 589 printf ("Public domain, by Alexander G. M. Smith.\n"); 590 printf ("$Id$\n"); 591 printf ("$HeadURL$\n"); 592 return -10; 593 } 594 595 NextArgIndex = 1; 596 if (strcmp (argv[NextArgIndex], "-s") == 0) 597 { 598 SaveSeparatorLine = true; 599 NextArgIndex++; 600 } 601 602 /* Try to open the input file. */ 603 604 if (NextArgIndex >= argc) 605 { 606 ReturnCode = -20; 607 DisplayErrorMessage ("Missing the input file (mbox file) name argument."); 608 goto ErrorExit; 609 } 610 strncpy (InputPathName, argv[NextArgIndex], sizeof (InputPathName) - 1); 611 NextArgIndex++; 612 InputFile = fopen (InputPathName, "rb"); 613 if (InputFile == NULL) 614 { 615 ReturnCode = errno; 616 sprintf (ErrorMessage, "Unable to open file \"%s\" for reading", 617 InputPathName); 618 DisplayErrorMessage (ErrorMessage, ReturnCode); 619 goto ErrorExit; 620 } 621 622 /* Try to make the output directory. First get its name. */ 623 624 if (NextArgIndex < argc) 625 { 626 strncpy (OutputDirectoryPathName, argv[NextArgIndex], 627 sizeof (OutputDirectoryPathName) - 2 628 /* Leave space for adding trailing slash and NUL byte */); 629 NextArgIndex++; 630 } 631 else 632 strcpy (OutputDirectoryPathName, "."); 633 634 /* Remove trailing '/' characters from the output directory path. */ 635 636 StringPntr = 637 OutputDirectoryPathName + (strlen (OutputDirectoryPathName) - 1); 638 while (StringPntr >= OutputDirectoryPathName) 639 { 640 if (*StringPntr != '/') 641 break; 642 StringPntr--; 643 } 644 *(++StringPntr) = 0; 645 646 if (StringPntr - OutputDirectoryPathName > 0 && 647 strcmp (OutputDirectoryPathName, ".") != 0) 648 { 649 if (mkdir (OutputDirectoryPathName, 0777)) 650 { 651 ReturnCode = errno; 652 if (ReturnCode != B_FILE_EXISTS) 653 { 654 sprintf (ErrorMessage, "Unable to make output directory \"%s\"", 655 OutputDirectoryPathName); 656 DisplayErrorMessage (ErrorMessage, ReturnCode); 657 goto ErrorExit; 658 } 659 } 660 } 661 662 /* Set the output BDirectory. */ 663 664 ReturnCode = OutputDir.SetTo (OutputDirectoryPathName); 665 if (ReturnCode != B_OK) 666 { 667 sprintf (ErrorMessage, "Unable to set output BDirectory to \"%s\"", 668 OutputDirectoryPathName); 669 DisplayErrorMessage (ErrorMessage, ReturnCode); 670 goto ErrorExit; 671 } 672 673 printf ("Input file: \"%s\", Output directory: \"%s\", ", 674 InputPathName, OutputDirectoryPathName); 675 printf ("%ssaving separator text line at the top of each message. Working", 676 SaveSeparatorLine ? "" : "not "); 677 678 /* Extract a text message from the mail file. It starts with a line that 679 says "From blah Day MM NN XX:XX:XX YYYY TZONE". Blah is an e-mail address 680 you can ignore (just treat it as a word separated by spaces). Day is a 3 681 letter day of the week. MM is a 3 letter month name. NN is the two digit 682 day of the week, has a leading space if the day is less than 10. XX:XX:XX is 683 the time, the X's are digits. YYYY is the four digit year. TZONE is the 684 optional time zone info, a plus or minus sign and 4 digits. */ 685 686 while (!feof (InputFile)) 687 { 688 /* First read in one line of text. */ 689 690 if (!fgets (TempString, sizeof (TempString), InputFile)) 691 { 692 ReturnCode = errno; 693 if (ferror (InputFile)) 694 { 695 sprintf (ErrorMessage, 696 "Error while reading from \"%s\"", InputPathName); 697 DisplayErrorMessage (ErrorMessage, ReturnCode); 698 goto ErrorExit; 699 } 700 break; /* No error, just end of file. */ 701 } 702 703 /* Remove any trailing control characters (line feed usually, or CRLF). 704 Might also nuke trailing tabs too. Doesn't usually matter. The main thing 705 is to allow input files with both LF and CRLF endings (and even CR endings 706 if you come from the Macintosh world). */ 707 708 StringPntr = TempString + strlen (TempString) - 1; 709 while (StringPntr >= TempString && *StringPntr < 32) 710 StringPntr--; 711 *(++StringPntr) = 0; 712 713 /* See if this is the start of a new message. */ 714 715 if (IsStartOfUsenetArticle (TempString) || 716 IsStartOfMailMessage (TempString)) 717 { 718 if (HaveOldMessage) 719 { 720 if ((ReturnCode = SaveMessage (MessageText)) != 0) 721 goto ErrorExit; 722 putchar ('.'); 723 fflush (stdout); 724 MessagesDoneCount++; 725 } 726 HaveOldMessage = true; 727 MessageText.SetTo (SaveSeparatorLine ? TempString : ""); 728 } 729 else 730 { 731 /* Append the line to the current message text. */ 732 733 if (MessageText.Length () > 0) 734 MessageText.Append ("\r\n"); /* Yes, BeMail expects CR/LF line ends. */ 735 MessageText.Append (TempString); 736 } 737 } 738 739 /* Flush out the last message. */ 740 741 if (HaveOldMessage) 742 { 743 if ((ReturnCode = SaveMessage (MessageText)) != 0) 744 goto ErrorExit; 745 putchar ('.'); 746 MessagesDoneCount++; 747 } 748 printf (" Did %d messages.\n", MessagesDoneCount); 749 750 ReturnCode = 0; 751 752 ErrorExit: 753 if (InputFile != NULL) 754 fclose (InputFile); 755 OutputDir.Unset (); 756 return ReturnCode; 757 } 758