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
BEmailMessage(BPositionIO * file,bool own,uint32 defaultCharSet)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
BEmailMessage(const entry_ref * ref,uint32 defaultCharSet)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
~BEmailMessage()90 BEmailMessage::~BEmailMessage()
91 {
92 free(fBCC);
93
94 delete fBody;
95 delete fData;
96 }
97
98
99 status_t
InitCheck() const100 BEmailMessage::InitCheck() const
101 {
102 return fStatus;
103 }
104
105
106 BEmailMessage*
ReplyMessage(mail_reply_to_mode replyTo,bool accountFromMail,const char * quoteStyle)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*
ForwardMessage(bool accountFromMail,bool includeAttachments)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*
To() const229 BEmailMessage::To() const
230 {
231 return HeaderField("To");
232 }
233
234
235 const char*
From() const236 BEmailMessage::From() const
237 {
238 return HeaderField("From");
239 }
240
241
242 const char*
ReplyTo() const243 BEmailMessage::ReplyTo() const
244 {
245 return HeaderField("Reply-To");
246 }
247
248
249 const char*
CC() const250 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*
Subject() const258 BEmailMessage::Subject() const
259 {
260 return HeaderField("Subject");
261 }
262
263
264 time_t
Date() const265 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
Priority() const276 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
SetSubject(const char * subject,uint32 charset,mail_encoding encoding)313 BEmailMessage::SetSubject(const char* subject, uint32 charset,
314 mail_encoding encoding)
315 {
316 SetHeaderField("Subject", subject, charset, encoding);
317 }
318
319
320 void
SetReplyTo(const char * replyTo,uint32 charset,mail_encoding encoding)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
SetFrom(const char * from,uint32 charset,mail_encoding encoding)329 BEmailMessage::SetFrom(const char* from, uint32 charset, mail_encoding encoding)
330 {
331 SetHeaderField("From", from, charset, encoding);
332 }
333
334
335 void
SetTo(const char * to,uint32 charset,mail_encoding encoding)336 BEmailMessage::SetTo(const char* to, uint32 charset, mail_encoding encoding)
337 {
338 SetHeaderField("To", to, charset, encoding);
339 }
340
341
342 void
SetCC(const char * cc,uint32 charset,mail_encoding encoding)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
SetBCC(const char * bcc)351 BEmailMessage::SetBCC(const char* bcc)
352 {
353 free(fBCC);
354 fBCC = strdup(bcc);
355 }
356
357
358 void
SetPriority(int to)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
GetName(char * name,int32 maxLength) const383 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
GetName(BString * name) const402 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
SendViaAccountFrom(BEmailMessage * message)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
SendViaAccount(const char * accountName)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
SendViaAccount(int32 account)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
Account() const453 BEmailMessage::Account() const
454 {
455 return fAccountID;
456 }
457
458
459 status_t
GetAccountName(BString & accountName) const460 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
AddComponent(BMailComponent * component)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
RemoveComponent(BMailComponent *)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
RemoveComponent(int32)523 BEmailMessage::RemoveComponent(int32 /*index*/)
524 {
525 // not yet implemented
526 return B_ERROR;
527 }
528
529
530 BMailComponent*
GetComponent(int32 i,bool parseNow)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
CountComponents() const545 BEmailMessage::CountComponents() const
546 {
547 return fComponentCount;
548 }
549
550
551 void
Attach(entry_ref * ref,bool includeAttributes)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
IsComponentAttachment(int32 i)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
SetBodyTextTo(const char * text)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*
Body()596 BEmailMessage::Body()
597 {
598 if (fTextBody == NULL)
599 fTextBody = _RetrieveTextBody(fBody);
600
601 return fTextBody;
602 }
603
604
605 const char*
BodyText()606 BEmailMessage::BodyText()
607 {
608 if (Body() == NULL)
609 return NULL;
610
611 return fTextBody->Text();
612 }
613
614
615 status_t
SetBody(BTextMailComponent * body)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*
_RetrieveTextBody(BMailComponent * component)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
SetToRFC822(BPositionIO * mailFile,size_t length,bool parseNow)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
RenderToRFC822(BPositionIO * file)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
RenderTo(BDirectory * dir,BEntry * msg)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
Send(bool sendNow)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
_ReservedMessage1()975 void BEmailMessage::_ReservedMessage1() {}
_ReservedMessage2()976 void BEmailMessage::_ReservedMessage2() {}
_ReservedMessage3()977 void BEmailMessage::_ReservedMessage3() {}
978