1 /* 2 * Copyright 2007-2015, Haiku Inc. All Rights Reserved. 3 * Copyright 2001-2004 Dr. Zoidberg Enterprises. All rights reserved. 4 * 5 * Distributed under the terms of the MIT License. 6 */ 7 8 9 //! The main general purpose mail message class 10 11 12 #include <MailMessage.h> 13 14 #include <ctype.h> 15 #include <stdio.h> 16 #include <stdlib.h> 17 #include <string.h> 18 #include <sys/utsname.h> 19 20 #include <parsedate.h> 21 22 #include <Directory.h> 23 #include <E-mail.h> 24 #include <Entry.h> 25 #include <File.h> 26 #include <FindDirectory.h> 27 #include <List.h> 28 #include <MailAttachment.h> 29 #include <MailDaemon.h> 30 #include <MailSettings.h> 31 #include <Messenger.h> 32 #include <netdb.h> 33 #include <NodeInfo.h> 34 #include <Path.h> 35 #include <String.h> 36 #include <StringList.h> 37 38 #include <MailPrivate.h> 39 #include <mail_util.h> 40 41 42 using namespace BPrivate; 43 44 45 //-------Change the following!---------------------- 46 #define mime_boundary "----------Zoidberg-BeMail-temp--------" 47 #define mime_warning "This is a multipart message in MIME format." 48 49 50 BEmailMessage::BEmailMessage(BPositionIO* file, bool own, uint32 defaultCharSet) 51 : 52 BMailContainer(defaultCharSet), 53 fData(NULL), 54 fStatus(B_NO_ERROR), 55 fBCC(NULL), 56 fComponentCount(0), 57 fBody(NULL), 58 fTextBody(NULL) 59 { 60 BMailSettings settings; 61 fAccountID = settings.DefaultOutboundAccount(); 62 63 if (own) 64 fData = file; 65 66 if (file != NULL) 67 SetToRFC822(file, ~0L); 68 } 69 70 71 BEmailMessage::BEmailMessage(const entry_ref* ref, uint32 defaultCharSet) 72 : 73 BMailContainer(defaultCharSet), 74 fBCC(NULL), 75 fComponentCount(0), 76 fBody(NULL), 77 fTextBody(NULL) 78 { 79 BMailSettings settings; 80 fAccountID = settings.DefaultOutboundAccount(); 81 82 fData = new BFile(); 83 fStatus = static_cast<BFile*>(fData)->SetTo(ref, B_READ_ONLY); 84 85 if (fStatus == B_OK) 86 SetToRFC822(fData, ~0L); 87 } 88 89 90 BEmailMessage::~BEmailMessage() 91 { 92 free(fBCC); 93 94 delete fBody; 95 delete fData; 96 } 97 98 99 status_t 100 BEmailMessage::InitCheck() const 101 { 102 return fStatus; 103 } 104 105 106 BEmailMessage* 107 BEmailMessage::ReplyMessage(mail_reply_to_mode replyTo, bool accountFromMail, 108 const char* quoteStyle) 109 { 110 BEmailMessage* reply = new BEmailMessage; 111 112 // Set ReplyTo: 113 114 if (replyTo == B_MAIL_REPLY_TO_ALL) { 115 reply->SetTo(From()); 116 117 BList list; 118 get_address_list(list, CC(), extract_address); 119 get_address_list(list, To(), extract_address); 120 121 // Filter out the sender 122 BMailAccounts accounts; 123 BMailAccountSettings* account = accounts.AccountByID(Account()); 124 BString sender; 125 if (account != NULL) 126 sender = account->ReturnAddress(); 127 extract_address(sender); 128 129 BString cc; 130 131 for (int32 i = list.CountItems(); i-- > 0;) { 132 char* address = (char*)list.RemoveItem((int32)0); 133 134 // Add everything which is not the sender and not already in the 135 // list 136 if (sender.ICompare(address) && cc.FindFirst(address) < 0) { 137 if (cc.Length() > 0) 138 cc << ", "; 139 140 cc << address; 141 } 142 143 free(address); 144 } 145 146 if (cc.Length() > 0) 147 reply->SetCC(cc.String()); 148 } else if (replyTo == B_MAIL_REPLY_TO_SENDER || ReplyTo() == NULL) 149 reply->SetTo(From()); 150 else 151 reply->SetTo(ReplyTo()); 152 153 // Set special "In-Reply-To:" header (used for threading) 154 const char* messageID = fBody ? fBody->HeaderField("Message-Id") : NULL; 155 if (messageID != NULL) 156 reply->SetHeaderField("In-Reply-To", messageID); 157 158 // quote body text 159 reply->SetBodyTextTo(BodyText()); 160 if (quoteStyle) 161 reply->Body()->Quote(quoteStyle); 162 163 // Set the subject (and add a "Re:" if needed) 164 BString string = Subject(); 165 if (string.ICompare("re:", 3) != 0) 166 string.Prepend("Re: "); 167 reply->SetSubject(string.String()); 168 169 // set the matching outbound chain 170 if (accountFromMail) 171 reply->SendViaAccountFrom(this); 172 173 return reply; 174 } 175 176 177 BEmailMessage* 178 BEmailMessage::ForwardMessage(bool accountFromMail, bool includeAttachments) 179 { 180 BString header = "------ Forwarded Message: ------\n"; 181 header << "To: " << To() << '\n'; 182 header << "From: " << From() << '\n'; 183 if (CC() != NULL) { 184 // Can use CC rather than "Cc" since display only. 185 header << "CC: " << CC() << '\n'; 186 } 187 header << "Subject: " << Subject() << '\n'; 188 header << "Date: " << HeaderField("Date") << "\n\n"; 189 if (fTextBody != NULL) 190 header << fTextBody->Text() << '\n'; 191 BEmailMessage *message = new BEmailMessage(); 192 message->SetBodyTextTo(header.String()); 193 194 // set the subject 195 BString subject = Subject(); 196 if (subject.IFindFirst("fwd") == B_ERROR 197 && subject.IFindFirst("forward") == B_ERROR 198 && subject.FindFirst("FW") == B_ERROR) 199 subject << " (fwd)"; 200 message->SetSubject(subject.String()); 201 202 if (includeAttachments) { 203 for (int32 i = 0; i < CountComponents(); i++) { 204 BMailComponent* component = GetComponent(i); 205 if (component == fTextBody || component == NULL) 206 continue; 207 208 //---I am ashamed to have the written the code between here and the next comment 209 // ... and you still managed to get it wrong ;-)), axeld. 210 // we should really move this stuff into copy constructors 211 // or something like that 212 213 BMallocIO io; 214 component->RenderToRFC822(&io); 215 BMailComponent* clone = component->WhatIsThis(); 216 io.Seek(0, SEEK_SET); 217 clone->SetToRFC822(&io, io.BufferLength(), true); 218 message->AddComponent(clone); 219 } 220 } 221 if (accountFromMail) 222 message->SendViaAccountFrom(this); 223 224 return message; 225 } 226 227 228 const char* 229 BEmailMessage::To() const 230 { 231 return HeaderField("To"); 232 } 233 234 235 const char* 236 BEmailMessage::From() const 237 { 238 return HeaderField("From"); 239 } 240 241 242 const char* 243 BEmailMessage::ReplyTo() const 244 { 245 return HeaderField("Reply-To"); 246 } 247 248 249 const char* 250 BEmailMessage::CC() const 251 { 252 return HeaderField("Cc"); 253 // Note case of CC is "Cc" in our internal headers. 254 } 255 256 257 const char* 258 BEmailMessage::Subject() const 259 { 260 return HeaderField("Subject"); 261 } 262 263 264 time_t 265 BEmailMessage::Date() const 266 { 267 const char* dateField = HeaderField("Date"); 268 if (dateField == NULL) 269 return -1; 270 271 return ParseDateWithTimeZone(dateField); 272 } 273 274 275 int 276 BEmailMessage::Priority() const 277 { 278 int priorityNumber; 279 const char* priorityString; 280 281 /* The usual values are a number from 1 to 5, or one of three words: 282 X-Priority: 1 and/or X-MSMail-Priority: High 283 X-Priority: 3 and/or X-MSMail-Priority: Normal 284 X-Priority: 5 and/or X-MSMail-Priority: Low 285 Also plain Priority: is "normal", "urgent" or "non-urgent", see RFC 1327. */ 286 287 priorityString = HeaderField("Priority"); 288 if (priorityString == NULL) 289 priorityString = HeaderField("X-Priority"); 290 if (priorityString == NULL) 291 priorityString = HeaderField("X-Msmail-Priority"); 292 if (priorityString == NULL) 293 return 3; 294 priorityNumber = atoi (priorityString); 295 if (priorityNumber != 0) { 296 if (priorityNumber > 5) 297 priorityNumber = 5; 298 if (priorityNumber < 1) 299 priorityNumber = 1; 300 return priorityNumber; 301 } 302 if (strcasecmp (priorityString, "Low") == 0 303 || strcasecmp (priorityString, "non-urgent") == 0) 304 return 5; 305 if (strcasecmp (priorityString, "High") == 0 306 || strcasecmp (priorityString, "urgent") == 0) 307 return 1; 308 return 3; 309 } 310 311 312 void 313 BEmailMessage::SetSubject(const char* subject, uint32 charset, 314 mail_encoding encoding) 315 { 316 SetHeaderField("Subject", subject, charset, encoding); 317 } 318 319 320 void 321 BEmailMessage::SetReplyTo(const char* replyTo, uint32 charset, 322 mail_encoding encoding) 323 { 324 SetHeaderField("Reply-To", replyTo, charset, encoding); 325 } 326 327 328 void 329 BEmailMessage::SetFrom(const char* from, uint32 charset, mail_encoding encoding) 330 { 331 SetHeaderField("From", from, charset, encoding); 332 } 333 334 335 void 336 BEmailMessage::SetTo(const char* to, uint32 charset, mail_encoding encoding) 337 { 338 SetHeaderField("To", to, charset, encoding); 339 } 340 341 342 void 343 BEmailMessage::SetCC(const char* cc, uint32 charset, mail_encoding encoding) 344 { 345 // For consistency with our header names, use Cc as the name. 346 SetHeaderField("Cc", cc, charset, encoding); 347 } 348 349 350 void 351 BEmailMessage::SetBCC(const char* bcc) 352 { 353 free(fBCC); 354 fBCC = strdup(bcc); 355 } 356 357 358 void 359 BEmailMessage::SetPriority(int to) 360 { 361 char tempString[20]; 362 363 if (to < 1) 364 to = 1; 365 if (to > 5) 366 to = 5; 367 sprintf (tempString, "%d", to); 368 SetHeaderField("X-Priority", tempString); 369 if (to <= 2) { 370 SetHeaderField("Priority", "urgent"); 371 SetHeaderField("X-Msmail-Priority", "High"); 372 } else if (to >= 4) { 373 SetHeaderField("Priority", "non-urgent"); 374 SetHeaderField("X-Msmail-Priority", "Low"); 375 } else { 376 SetHeaderField("Priority", "normal"); 377 SetHeaderField("X-Msmail-Priority", "Normal"); 378 } 379 } 380 381 382 status_t 383 BEmailMessage::GetName(char* name, int32 maxLength) const 384 { 385 if (name == NULL || maxLength <= 0) 386 return B_BAD_VALUE; 387 388 if (BFile* file = dynamic_cast<BFile*>(fData)) { 389 status_t status = file->ReadAttr(B_MAIL_ATTR_NAME, B_STRING_TYPE, 0, 390 name, maxLength); 391 name[maxLength - 1] = '\0'; 392 393 return status >= 0 ? B_OK : status; 394 } 395 // TODO: look at From header? But usually there is 396 // a file since only the BeMail GUI calls this. 397 return B_ERROR; 398 } 399 400 401 status_t 402 BEmailMessage::GetName(BString* name) const 403 { 404 char* buffer = name->LockBuffer(B_FILE_NAME_LENGTH); 405 status_t status = GetName(buffer, B_FILE_NAME_LENGTH); 406 name->UnlockBuffer(); 407 408 return status; 409 } 410 411 412 void 413 BEmailMessage::SendViaAccountFrom(BEmailMessage* message) 414 { 415 BString name; 416 if (message->GetAccountName(name) < B_OK) { 417 // just return the message with the default account 418 return; 419 } 420 421 SendViaAccount(name); 422 } 423 424 425 void 426 BEmailMessage::SendViaAccount(const char* accountName) 427 { 428 BMailAccounts accounts; 429 BMailAccountSettings* account = accounts.AccountByName(accountName); 430 if (account != NULL) 431 SendViaAccount(account->AccountID()); 432 } 433 434 435 void 436 BEmailMessage::SendViaAccount(int32 account) 437 { 438 fAccountID = account; 439 440 BMailAccounts accounts; 441 BMailAccountSettings* accountSettings = accounts.AccountByID(fAccountID); 442 443 BString from; 444 if (accountSettings) { 445 from << '\"' << accountSettings->RealName() << "\" <" 446 << accountSettings->ReturnAddress() << '>'; 447 } 448 SetFrom(from); 449 } 450 451 452 int32 453 BEmailMessage::Account() const 454 { 455 return fAccountID; 456 } 457 458 459 status_t 460 BEmailMessage::GetAccountName(BString& accountName) const 461 { 462 BFile* file = dynamic_cast<BFile*>(fData); 463 if (file == NULL) 464 return B_ERROR; 465 466 int32 accountID; 467 size_t read = file->ReadAttr(B_MAIL_ATTR_ACCOUNT, B_INT32_TYPE, 0, 468 &accountID, sizeof(int32)); 469 if (read < sizeof(int32)) 470 return B_ERROR; 471 472 BMailAccounts accounts; 473 BMailAccountSettings* account = accounts.AccountByID(accountID); 474 if (account != NULL) 475 accountName = account->Name(); 476 else 477 accountName = ""; 478 479 return B_OK; 480 } 481 482 483 status_t 484 BEmailMessage::AddComponent(BMailComponent* component) 485 { 486 status_t status = B_OK; 487 488 if (fComponentCount == 0) 489 fBody = component; 490 else if (fComponentCount == 1) { 491 BMIMEMultipartMailContainer *container 492 = new BMIMEMultipartMailContainer( 493 mime_boundary, mime_warning, _charSetForTextDecoding); 494 status = container->AddComponent(fBody); 495 if (status == B_OK) 496 status = container->AddComponent(component); 497 fBody = container; 498 } else { 499 BMIMEMultipartMailContainer* container 500 = dynamic_cast<BMIMEMultipartMailContainer*>(fBody); 501 if (container == NULL) 502 return B_MISMATCHED_VALUES; 503 504 status = container->AddComponent(component); 505 } 506 507 if (status == B_OK) 508 fComponentCount++; 509 return status; 510 } 511 512 513 status_t 514 BEmailMessage::RemoveComponent(BMailComponent* /*component*/) 515 { 516 // not yet implemented 517 // BeMail/Enclosures.cpp:169: contains a warning about this fact 518 return B_ERROR; 519 } 520 521 522 status_t 523 BEmailMessage::RemoveComponent(int32 /*index*/) 524 { 525 // not yet implemented 526 return B_ERROR; 527 } 528 529 530 BMailComponent* 531 BEmailMessage::GetComponent(int32 i, bool parseNow) 532 { 533 if (BMIMEMultipartMailContainer* container 534 = dynamic_cast<BMIMEMultipartMailContainer*>(fBody)) 535 return container->GetComponent(i, parseNow); 536 537 if (i < fComponentCount) 538 return fBody; 539 540 return NULL; 541 } 542 543 544 int32 545 BEmailMessage::CountComponents() const 546 { 547 return fComponentCount; 548 } 549 550 551 void 552 BEmailMessage::Attach(entry_ref* ref, bool includeAttributes) 553 { 554 if (includeAttributes) 555 AddComponent(new BAttributedMailAttachment(ref)); 556 else 557 AddComponent(new BSimpleMailAttachment(ref)); 558 } 559 560 561 bool 562 BEmailMessage::IsComponentAttachment(int32 i) 563 { 564 if ((i >= fComponentCount) || (fComponentCount == 0)) 565 return false; 566 567 if (fComponentCount == 1) 568 return fBody->IsAttachment(); 569 570 BMIMEMultipartMailContainer* container 571 = dynamic_cast<BMIMEMultipartMailContainer*>(fBody); 572 if (container == NULL) 573 return false; 574 575 BMailComponent* component = container->GetComponent(i); 576 if (component == NULL) 577 return false; 578 579 return component->IsAttachment(); 580 } 581 582 583 void 584 BEmailMessage::SetBodyTextTo(const char* text) 585 { 586 if (fTextBody == NULL) { 587 fTextBody = new BTextMailComponent; 588 AddComponent(fTextBody); 589 } 590 591 fTextBody->SetText(text); 592 } 593 594 595 BTextMailComponent* 596 BEmailMessage::Body() 597 { 598 if (fTextBody == NULL) 599 fTextBody = _RetrieveTextBody(fBody); 600 601 return fTextBody; 602 } 603 604 605 const char* 606 BEmailMessage::BodyText() 607 { 608 if (Body() == NULL) 609 return NULL; 610 611 return fTextBody->Text(); 612 } 613 614 615 status_t 616 BEmailMessage::SetBody(BTextMailComponent* body) 617 { 618 if (fTextBody != NULL) { 619 return B_ERROR; 620 // removing doesn't exist for now 621 // RemoveComponent(fTextBody); 622 // delete fTextBody; 623 } 624 fTextBody = body; 625 AddComponent(fTextBody); 626 627 return B_OK; 628 } 629 630 631 BTextMailComponent* 632 BEmailMessage::_RetrieveTextBody(BMailComponent* component) 633 { 634 BTextMailComponent* body = dynamic_cast<BTextMailComponent*>(component); 635 if (body != NULL) 636 return body; 637 638 BMIMEMultipartMailContainer* container 639 = dynamic_cast<BMIMEMultipartMailContainer*>(component); 640 if (container != NULL) { 641 for (int32 i = 0; i < container->CountComponents(); i++) { 642 if ((component = container->GetComponent(i)) == NULL) 643 continue; 644 645 switch (component->ComponentType()) { 646 case B_MAIL_PLAIN_TEXT_BODY: 647 // AttributedAttachment returns the MIME type of its 648 // contents, so we have to use dynamic_cast here 649 body = dynamic_cast<BTextMailComponent*>( 650 container->GetComponent(i)); 651 if (body != NULL) 652 return body; 653 break; 654 655 case B_MAIL_MULTIPART_CONTAINER: 656 body = _RetrieveTextBody(container->GetComponent(i)); 657 if (body != NULL) 658 return body; 659 break; 660 } 661 } 662 } 663 return NULL; 664 } 665 666 667 status_t 668 BEmailMessage::SetToRFC822(BPositionIO* mailFile, size_t length, 669 bool parseNow) 670 { 671 if (BFile* file = dynamic_cast<BFile*>(mailFile)) { 672 file->ReadAttr(B_MAIL_ATTR_ACCOUNT_ID, B_INT32_TYPE, 0, &fAccountID, 673 sizeof(fAccountID)); 674 } 675 676 mailFile->Seek(0, SEEK_END); 677 length = mailFile->Position(); 678 mailFile->Seek(0, SEEK_SET); 679 680 fStatus = BMailComponent::SetToRFC822(mailFile, length, parseNow); 681 if (fStatus < B_OK) 682 return fStatus; 683 684 fBody = WhatIsThis(); 685 686 mailFile->Seek(0, SEEK_SET); 687 fStatus = fBody->SetToRFC822(mailFile, length, parseNow); 688 if (fStatus < B_OK) 689 return fStatus; 690 691 // Move headers that we use to us, everything else to fBody 692 const char* name; 693 for (int32 i = 0; (name = fBody->HeaderAt(i)) != NULL; i++) { 694 if (strcasecmp(name, "Subject") != 0 695 && strcasecmp(name, "To") != 0 696 && strcasecmp(name, "From") != 0 697 && strcasecmp(name, "Reply-To") != 0 698 && strcasecmp(name, "Cc") != 0 699 && strcasecmp(name, "Priority") != 0 700 && strcasecmp(name, "X-Priority") != 0 701 && strcasecmp(name, "X-Msmail-Priority") != 0 702 && strcasecmp(name, "Date") != 0) { 703 RemoveHeader(name); 704 } 705 } 706 707 fBody->RemoveHeader("Subject"); 708 fBody->RemoveHeader("To"); 709 fBody->RemoveHeader("From"); 710 fBody->RemoveHeader("Reply-To"); 711 fBody->RemoveHeader("Cc"); 712 fBody->RemoveHeader("Priority"); 713 fBody->RemoveHeader("X-Priority"); 714 fBody->RemoveHeader("X-Msmail-Priority"); 715 fBody->RemoveHeader("Date"); 716 717 fComponentCount = 1; 718 if (BMIMEMultipartMailContainer* container 719 = dynamic_cast<BMIMEMultipartMailContainer*>(fBody)) 720 fComponentCount = container->CountComponents(); 721 722 return B_OK; 723 } 724 725 726 status_t 727 BEmailMessage::RenderToRFC822(BPositionIO* file) 728 { 729 if (fBody == NULL) 730 return B_MAIL_INVALID_MAIL; 731 732 // Do real rendering 733 734 if (From() == NULL) { 735 // set the "From:" string 736 SendViaAccount(fAccountID); 737 } 738 739 BList recipientList; 740 get_address_list(recipientList, To(), extract_address); 741 get_address_list(recipientList, CC(), extract_address); 742 get_address_list(recipientList, fBCC, extract_address); 743 744 BString recipients; 745 for (int32 i = recipientList.CountItems(); i-- > 0;) { 746 char *address = (char *)recipientList.RemoveItem((int32)0); 747 748 recipients << '<' << address << '>'; 749 if (i) 750 recipients << ','; 751 752 free(address); 753 } 754 755 // add the date field 756 time_t creationTime = time(NULL); 757 { 758 char date[128]; 759 struct tm tm; 760 localtime_r(&creationTime, &tm); 761 762 size_t length = strftime(date, sizeof(date), 763 "%a, %d %b %Y %H:%M:%S", &tm); 764 765 // GMT offsets are full hours, yes, but you never know :-) 766 snprintf(date + length, sizeof(date) - length, " %+03d%02d", 767 tm.tm_gmtoff / 3600, (tm.tm_gmtoff / 60) % 60); 768 769 SetHeaderField("Date", date); 770 } 771 772 // add a message-id 773 774 // empirical evidence indicates message id must be enclosed in 775 // angle brackets and there must be an "at" symbol in it 776 BString messageID; 777 messageID << "<"; 778 messageID << system_time(); 779 messageID << "-BeMail@"; 780 781 char host[255]; 782 if (gethostname(host, sizeof(host)) < 0 || !host[0]) 783 strcpy(host, "zoidberg"); 784 785 messageID << host; 786 messageID << ">"; 787 788 SetHeaderField("Message-Id", messageID.String()); 789 790 status_t err = BMailComponent::RenderToRFC822(file); 791 if (err < B_OK) 792 return err; 793 794 file->Seek(-2, SEEK_CUR); 795 // Remove division between headers 796 797 err = fBody->RenderToRFC822(file); 798 if (err < B_OK) 799 return err; 800 801 // Set the message file's attributes. Do this after the rest of the file 802 // is filled in, in case the daemon attempts to send it before it is ready 803 // (since the daemon may send it when it sees the status attribute getting 804 // set to "Pending"). 805 806 if (BFile* attributed = dynamic_cast <BFile*>(file)) { 807 BNodeInfo(attributed).SetType(B_MAIL_TYPE); 808 809 attributed->WriteAttrString(B_MAIL_ATTR_RECIPIENTS,&recipients); 810 811 BString attr; 812 813 attr = To(); 814 attributed->WriteAttrString(B_MAIL_ATTR_TO, &attr); 815 attr = CC(); 816 attributed->WriteAttrString(B_MAIL_ATTR_CC, &attr); 817 attr = Subject(); 818 attributed->WriteAttrString(B_MAIL_ATTR_SUBJECT, &attr); 819 attr = ReplyTo(); 820 attributed->WriteAttrString(B_MAIL_ATTR_REPLY, &attr); 821 attr = From(); 822 attributed->WriteAttrString(B_MAIL_ATTR_FROM, &attr); 823 if (Priority() != 3 /* Normal is 3 */) { 824 sprintf(attr.LockBuffer(40), "%d", Priority()); 825 attr.UnlockBuffer(-1); 826 attributed->WriteAttrString(B_MAIL_ATTR_PRIORITY, &attr); 827 } 828 attr = "Pending"; 829 attributed->WriteAttrString(B_MAIL_ATTR_STATUS, &attr); 830 attr = "1.0"; 831 attributed->WriteAttrString(B_MAIL_ATTR_MIME, &attr); 832 833 attributed->WriteAttr(B_MAIL_ATTR_ACCOUNT, B_INT32_TYPE, 0, 834 &fAccountID, sizeof(int32)); 835 836 attributed->WriteAttr(B_MAIL_ATTR_WHEN, B_TIME_TYPE, 0, &creationTime, 837 sizeof(int32)); 838 int32 flags = B_MAIL_PENDING | B_MAIL_SAVE; 839 attributed->WriteAttr(B_MAIL_ATTR_FLAGS, B_INT32_TYPE, 0, &flags, 840 sizeof(int32)); 841 842 attributed->WriteAttr(B_MAIL_ATTR_ACCOUNT_ID, B_INT32_TYPE, 0, 843 &fAccountID, sizeof(int32)); 844 } 845 846 return B_OK; 847 } 848 849 850 status_t 851 BEmailMessage::RenderTo(BDirectory* dir, BEntry* msg) 852 { 853 time_t currentTime; 854 char numericDateString[40]; 855 struct tm timeFields; 856 BString worker; 857 858 // Generate a file name for the outgoing message. See also 859 // FolderFilter::ProcessMailMessage which does something similar for 860 // incoming messages. 861 862 BString name = Subject(); 863 SubjectToThread(name); 864 // Extract the core subject words. 865 if (name.Length() <= 0) 866 name = "No Subject"; 867 if (name[0] == '.') { 868 // Avoid hidden files, starting with a dot. 869 name.Prepend("_"); 870 } 871 872 // Convert the date into a year-month-day fixed digit width format, so that 873 // sorting by file name will give all the messages with the same subject in 874 // order of date. 875 time (¤tTime); 876 localtime_r (¤tTime, &timeFields); 877 sprintf (numericDateString, "%04d%02d%02d%02d%02d%02d", 878 timeFields.tm_year + 1900, timeFields.tm_mon + 1, timeFields.tm_mday, 879 timeFields.tm_hour, timeFields.tm_min, timeFields.tm_sec); 880 name << " " << numericDateString; 881 882 worker = From(); 883 extract_address_name(worker); 884 name << " " << worker; 885 886 name.Truncate(222); // reserve space for the uniquer 887 888 // Get rid of annoying characters which are hard to use in the shell. 889 name.ReplaceAll('/','_'); 890 name.ReplaceAll('\'','_'); 891 name.ReplaceAll('"','_'); 892 name.ReplaceAll('!','_'); 893 name.ReplaceAll('<','_'); 894 name.ReplaceAll('>','_'); 895 896 // Remove multiple spaces. 897 while (name.FindFirst(" ") >= 0) 898 name.Replace(" ", " ", 1024); 899 900 int32 uniquer = time(NULL); 901 worker = name; 902 903 int32 tries = 30; 904 bool exists; 905 while ((exists = dir->Contains(worker.String())) && --tries > 0) { 906 srand(rand()); 907 uniquer += (rand() >> 16) - 16384; 908 909 worker = name; 910 worker << ' ' << uniquer; 911 } 912 913 if (exists) 914 printf("could not create mail! (should be: %s)\n", worker.String()); 915 916 BFile file; 917 status_t status = dir->CreateFile(worker.String(), &file); 918 if (status != B_OK) 919 return status; 920 921 if (msg != NULL) 922 msg->SetTo(dir,worker.String()); 923 924 return RenderToRFC822(&file); 925 } 926 927 928 status_t 929 BEmailMessage::Send(bool sendNow) 930 { 931 BMailAccounts accounts; 932 BMailAccountSettings* account = accounts.AccountByID(fAccountID); 933 if (account == NULL || !account->HasOutbound()) { 934 account = accounts.AccountByID( 935 BMailSettings().DefaultOutboundAccount()); 936 if (!account) 937 return B_ERROR; 938 SendViaAccount(account->AccountID()); 939 } 940 941 BString path; 942 if (account->OutboundSettings().FindString("path", &path) != B_OK) { 943 BPath defaultMailOutPath; 944 if (find_directory(B_USER_DIRECTORY, &defaultMailOutPath) != B_OK 945 || defaultMailOutPath.Append("mail/out") != B_OK) 946 path = "/boot/home/mail/out"; 947 else 948 path = defaultMailOutPath.Path(); 949 } 950 951 create_directory(path.String(), 0777); 952 BDirectory directory(path.String()); 953 954 BEntry message; 955 956 status_t status = RenderTo(&directory, &message); 957 if (status >= B_OK && sendNow) { 958 // TODO: check whether or not the internet connection is available 959 BMessenger daemon(B_MAIL_DAEMON_SIGNATURE); 960 if (!daemon.IsValid()) 961 return B_MAIL_NO_DAEMON; 962 963 BMessage msg(kMsgSendMessages); 964 msg.AddInt32("account", fAccountID); 965 BPath path; 966 message.GetPath(&path); 967 msg.AddString("message_path", path.Path()); 968 daemon.SendMessage(&msg); 969 } 970 971 return status; 972 } 973 974 975 void BEmailMessage::_ReservedMessage1() {} 976 void BEmailMessage::_ReservedMessage2() {} 977 void BEmailMessage::_ReservedMessage3() {} 978