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