xref: /haiku/src/kits/mail/MailMessage.cpp (revision 6d2f2ec177bf615a117a7428d71be4330545b320)
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: " << 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()
230 {
231 	return HeaderField("To");
232 }
233 
234 
235 const char*
236 BEmailMessage::From()
237 {
238 	return HeaderField("From");
239 }
240 
241 
242 const char*
243 BEmailMessage::ReplyTo()
244 {
245 	return HeaderField("Reply-To");
246 }
247 
248 
249 const char*
250 BEmailMessage::CC()
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()
259 {
260 	return HeaderField("Subject");
261 }
262 
263 
264 const char*
265 BEmailMessage::Date()
266 {
267 	return HeaderField("Date");
268 }
269 
270 
271 int
272 BEmailMessage::Priority()
273 {
274 	int priorityNumber;
275 	const char* priorityString;
276 
277 	/* The usual values are a number from 1 to 5, or one of three words:
278 	X-Priority: 1 and/or X-MSMail-Priority: High
279 	X-Priority: 3 and/or X-MSMail-Priority: Normal
280 	X-Priority: 5 and/or X-MSMail-Priority: Low
281 	Also plain Priority: is "normal", "urgent" or "non-urgent", see RFC 1327. */
282 
283 	priorityString = HeaderField("Priority");
284 	if (priorityString == NULL)
285 		priorityString = HeaderField("X-Priority");
286 	if (priorityString == NULL)
287 		priorityString = HeaderField("X-Msmail-Priority");
288 	if (priorityString == NULL)
289 		return 3;
290 	priorityNumber = atoi (priorityString);
291 	if (priorityNumber != 0) {
292 		if (priorityNumber > 5)
293 			priorityNumber = 5;
294 		if (priorityNumber < 1)
295 			priorityNumber = 1;
296 		return priorityNumber;
297 	}
298 	if (strcasecmp (priorityString, "Low") == 0
299 		|| strcasecmp (priorityString, "non-urgent") == 0)
300 		return 5;
301 	if (strcasecmp (priorityString, "High") == 0
302 		|| strcasecmp (priorityString, "urgent") == 0)
303 		return 1;
304 	return 3;
305 }
306 
307 
308 void
309 BEmailMessage::SetSubject(const char* subject, uint32 charset,
310 	mail_encoding encoding)
311 {
312 	SetHeaderField("Subject", subject, charset, encoding);
313 }
314 
315 
316 void
317 BEmailMessage::SetReplyTo(const char* replyTo, uint32 charset,
318 	mail_encoding encoding)
319 {
320 	SetHeaderField("Reply-To", replyTo, charset, encoding);
321 }
322 
323 
324 void
325 BEmailMessage::SetFrom(const char* from, uint32 charset, mail_encoding encoding)
326 {
327 	SetHeaderField("From", from, charset, encoding);
328 }
329 
330 
331 void
332 BEmailMessage::SetTo(const char* to, uint32 charset, mail_encoding encoding)
333 {
334 	SetHeaderField("To", to, charset, encoding);
335 }
336 
337 
338 void
339 BEmailMessage::SetCC(const char* cc, uint32 charset, mail_encoding encoding)
340 {
341 	// For consistency with our header names, use Cc as the name.
342 	SetHeaderField("Cc", cc, charset, encoding);
343 }
344 
345 
346 void
347 BEmailMessage::SetBCC(const char* bcc)
348 {
349 	free(fBCC);
350 	fBCC = strdup(bcc);
351 }
352 
353 
354 void
355 BEmailMessage::SetPriority(int to)
356 {
357 	char tempString[20];
358 
359 	if (to < 1)
360 		to = 1;
361 	if (to > 5)
362 		to = 5;
363 	sprintf (tempString, "%d", to);
364 	SetHeaderField("X-Priority", tempString);
365 	if (to <= 2) {
366 		SetHeaderField("Priority", "urgent");
367 		SetHeaderField("X-Msmail-Priority", "High");
368 	} else if (to >= 4) {
369 		SetHeaderField("Priority", "non-urgent");
370 		SetHeaderField("X-Msmail-Priority", "Low");
371 	} else {
372 		SetHeaderField("Priority", "normal");
373 		SetHeaderField("X-Msmail-Priority", "Normal");
374 	}
375 }
376 
377 
378 status_t
379 BEmailMessage::GetName(char* name, int32 maxLength) const
380 {
381 	if (name == NULL || maxLength <= 0)
382 		return B_BAD_VALUE;
383 
384 	if (BFile* file = dynamic_cast<BFile*>(fData)) {
385 		status_t status = file->ReadAttr(B_MAIL_ATTR_NAME, B_STRING_TYPE, 0,
386 			name, maxLength);
387 		name[maxLength - 1] = '\0';
388 
389 		return status >= 0 ? B_OK : status;
390 	}
391 	// TODO: look at From header?  But usually there is
392 	// a file since only the BeMail GUI calls this.
393 	return B_ERROR;
394 }
395 
396 
397 status_t
398 BEmailMessage::GetName(BString* name) const
399 {
400 	char* buffer = name->LockBuffer(B_FILE_NAME_LENGTH);
401 	status_t status = GetName(buffer, B_FILE_NAME_LENGTH);
402 	name->UnlockBuffer();
403 
404 	return status;
405 }
406 
407 
408 void
409 BEmailMessage::SendViaAccountFrom(BEmailMessage* message)
410 {
411 	BString name;
412 	if (message->GetAccountName(name) < B_OK) {
413 		// just return the message with the default account
414 		return;
415 	}
416 
417 	SendViaAccount(name);
418 }
419 
420 
421 void
422 BEmailMessage::SendViaAccount(const char* accountName)
423 {
424 	BMailAccounts accounts;
425 	BMailAccountSettings* account = accounts.AccountByName(accountName);
426 	if (account != NULL)
427 		SendViaAccount(account->AccountID());
428 }
429 
430 
431 void
432 BEmailMessage::SendViaAccount(int32 account)
433 {
434 	fAccountID = account;
435 
436 	BMailAccounts accounts;
437 	BMailAccountSettings* accountSettings = accounts.AccountByID(fAccountID);
438 
439 	BString from;
440 	if (accountSettings) {
441 		from << '\"' << accountSettings->RealName() << "\" <"
442 			<< accountSettings->ReturnAddress() << '>';
443 	}
444 	SetFrom(from);
445 }
446 
447 
448 int32
449 BEmailMessage::Account() const
450 {
451 	return fAccountID;
452 }
453 
454 
455 status_t
456 BEmailMessage::GetAccountName(BString& accountName) const
457 {
458 	BFile* file = dynamic_cast<BFile*>(fData);
459 	if (file == NULL)
460 		return B_ERROR;
461 
462 	int32 accountID;
463 	size_t read = file->ReadAttr(B_MAIL_ATTR_ACCOUNT, B_INT32_TYPE, 0,
464 		&accountID, sizeof(int32));
465 	if (read < sizeof(int32))
466 		return B_ERROR;
467 
468 	BMailAccounts accounts;
469 	BMailAccountSettings* account =  accounts.AccountByID(accountID);
470 	if (account != NULL)
471 		accountName = account->Name();
472 	else
473 		accountName = "";
474 
475 	return B_OK;
476 }
477 
478 
479 status_t
480 BEmailMessage::AddComponent(BMailComponent* component)
481 {
482 	status_t status = B_OK;
483 
484 	if (fComponentCount == 0)
485 		fBody = component;
486 	else if (fComponentCount == 1) {
487 		BMIMEMultipartMailContainer *container
488 			= new BMIMEMultipartMailContainer(
489 				mime_boundary, mime_warning, _charSetForTextDecoding);
490 		status = container->AddComponent(fBody);
491 		if (status == B_OK)
492 			status = container->AddComponent(component);
493 		fBody = container;
494 	} else {
495 		BMIMEMultipartMailContainer* container
496 			= dynamic_cast<BMIMEMultipartMailContainer*>(fBody);
497 		if (container == NULL)
498 			return B_MISMATCHED_VALUES;
499 
500 		status = container->AddComponent(component);
501 	}
502 
503 	if (status == B_OK)
504 		fComponentCount++;
505 	return status;
506 }
507 
508 
509 status_t
510 BEmailMessage::RemoveComponent(BMailComponent* /*component*/)
511 {
512 	// not yet implemented
513 	// BeMail/Enclosures.cpp:169: contains a warning about this fact
514 	return B_ERROR;
515 }
516 
517 
518 status_t
519 BEmailMessage::RemoveComponent(int32 /*index*/)
520 {
521 	// not yet implemented
522 	return B_ERROR;
523 }
524 
525 
526 BMailComponent*
527 BEmailMessage::GetComponent(int32 i, bool parseNow)
528 {
529 	if (BMIMEMultipartMailContainer* container
530 			= dynamic_cast<BMIMEMultipartMailContainer*>(fBody))
531 		return container->GetComponent(i, parseNow);
532 
533 	if (i < fComponentCount)
534 		return fBody;
535 
536 	return NULL;
537 }
538 
539 
540 int32
541 BEmailMessage::CountComponents() const
542 {
543 	return fComponentCount;
544 }
545 
546 
547 void
548 BEmailMessage::Attach(entry_ref* ref, bool includeAttributes)
549 {
550 	if (includeAttributes)
551 		AddComponent(new BAttributedMailAttachment(ref));
552 	else
553 		AddComponent(new BSimpleMailAttachment(ref));
554 }
555 
556 
557 bool
558 BEmailMessage::IsComponentAttachment(int32 i)
559 {
560 	if ((i >= fComponentCount) || (fComponentCount == 0))
561 		return false;
562 
563 	if (fComponentCount == 1)
564 		return fBody->IsAttachment();
565 
566 	BMIMEMultipartMailContainer* container
567 		= dynamic_cast<BMIMEMultipartMailContainer*>(fBody);
568 	if (container == NULL)
569 		return false;
570 
571 	BMailComponent* component = container->GetComponent(i);
572 	if (component == NULL)
573 		return false;
574 
575 	return component->IsAttachment();
576 }
577 
578 
579 void
580 BEmailMessage::SetBodyTextTo(const char* text)
581 {
582 	if (fTextBody == NULL) {
583 		fTextBody = new BTextMailComponent;
584 		AddComponent(fTextBody);
585 	}
586 
587 	fTextBody->SetText(text);
588 }
589 
590 
591 BTextMailComponent*
592 BEmailMessage::Body()
593 {
594 	if (fTextBody == NULL)
595 		fTextBody = _RetrieveTextBody(fBody);
596 
597 	return fTextBody;
598 }
599 
600 
601 const char*
602 BEmailMessage::BodyText()
603 {
604 	if (Body() == NULL)
605 		return NULL;
606 
607 	return fTextBody->Text();
608 }
609 
610 
611 status_t
612 BEmailMessage::SetBody(BTextMailComponent* body)
613 {
614 	if (fTextBody != NULL) {
615 		return B_ERROR;
616 //	removing doesn't exist for now
617 //		RemoveComponent(fTextBody);
618 //		delete fTextBody;
619 	}
620 	fTextBody = body;
621 	AddComponent(fTextBody);
622 
623 	return B_OK;
624 }
625 
626 
627 BTextMailComponent*
628 BEmailMessage::_RetrieveTextBody(BMailComponent* component)
629 {
630 	BTextMailComponent* body = dynamic_cast<BTextMailComponent*>(component);
631 	if (body != NULL)
632 		return body;
633 
634 	BMIMEMultipartMailContainer* container
635 		= dynamic_cast<BMIMEMultipartMailContainer*>(component);
636 	if (container != NULL) {
637 		for (int32 i = 0; i < container->CountComponents(); i++) {
638 			if ((component = container->GetComponent(i)) == NULL)
639 				continue;
640 
641 			switch (component->ComponentType()) {
642 				case B_MAIL_PLAIN_TEXT_BODY:
643 					// AttributedAttachment returns the MIME type of its
644 					// contents, so we have to use dynamic_cast here
645 					body = dynamic_cast<BTextMailComponent*>(
646 						container->GetComponent(i));
647 					if (body != NULL)
648 						return body;
649 					break;
650 
651 				case B_MAIL_MULTIPART_CONTAINER:
652 					body = _RetrieveTextBody(container->GetComponent(i));
653 					if (body != NULL)
654 						return body;
655 					break;
656 			}
657 		}
658 	}
659 	return NULL;
660 }
661 
662 
663 status_t
664 BEmailMessage::SetToRFC822(BPositionIO* mailFile, size_t length,
665 	bool parseNow)
666 {
667 	if (BFile* file = dynamic_cast<BFile*>(mailFile)) {
668 		file->ReadAttr(B_MAIL_ATTR_ACCOUNT_ID, B_INT32_TYPE, 0, &fAccountID,
669 			sizeof(fAccountID));
670 	}
671 
672 	mailFile->Seek(0, SEEK_END);
673 	length = mailFile->Position();
674 	mailFile->Seek(0, SEEK_SET);
675 
676 	fStatus = BMailComponent::SetToRFC822(mailFile, length, parseNow);
677 	if (fStatus < B_OK)
678 		return fStatus;
679 
680 	fBody = WhatIsThis();
681 
682 	mailFile->Seek(0, SEEK_SET);
683 	fStatus = fBody->SetToRFC822(mailFile, length, parseNow);
684 	if (fStatus < B_OK)
685 		return fStatus;
686 
687 	// Move headers that we use to us, everything else to fBody
688 	const char* name;
689 	for (int32 i = 0; (name = fBody->HeaderAt(i)) != NULL; i++) {
690 		if (strcasecmp(name, "Subject") != 0
691 			&& strcasecmp(name, "To") != 0
692 			&& strcasecmp(name, "From") != 0
693 			&& strcasecmp(name, "Reply-To") != 0
694 			&& strcasecmp(name, "Cc") != 0
695 			&& strcasecmp(name, "Priority") != 0
696 			&& strcasecmp(name, "X-Priority") != 0
697 			&& strcasecmp(name, "X-Msmail-Priority") != 0
698 			&& strcasecmp(name, "Date") != 0) {
699 			RemoveHeader(name);
700 		}
701 	}
702 
703 	fBody->RemoveHeader("Subject");
704 	fBody->RemoveHeader("To");
705 	fBody->RemoveHeader("From");
706 	fBody->RemoveHeader("Reply-To");
707 	fBody->RemoveHeader("Cc");
708 	fBody->RemoveHeader("Priority");
709 	fBody->RemoveHeader("X-Priority");
710 	fBody->RemoveHeader("X-Msmail-Priority");
711 	fBody->RemoveHeader("Date");
712 
713 	fComponentCount = 1;
714 	if (BMIMEMultipartMailContainer* container
715 			= dynamic_cast<BMIMEMultipartMailContainer*>(fBody))
716 		fComponentCount = container->CountComponents();
717 
718 	return B_OK;
719 }
720 
721 
722 status_t
723 BEmailMessage::RenderToRFC822(BPositionIO* file)
724 {
725 	if (fBody == NULL)
726 		return B_MAIL_INVALID_MAIL;
727 
728 	// Do real rendering
729 
730 	if (From() == NULL) {
731 		// set the "From:" string
732 		SendViaAccount(fAccountID);
733 	}
734 
735 	BList recipientList;
736 	get_address_list(recipientList, To(), extract_address);
737 	get_address_list(recipientList, CC(), extract_address);
738 	get_address_list(recipientList, fBCC, extract_address);
739 
740 	BString recipients;
741 	for (int32 i = recipientList.CountItems(); i-- > 0;) {
742 		char *address = (char *)recipientList.RemoveItem((int32)0);
743 
744 		recipients << '<' << address << '>';
745 		if (i)
746 			recipients << ',';
747 
748 		free(address);
749 	}
750 
751 	// add the date field
752 	int32 creationTime = time(NULL);
753 	{
754 		char date[128];
755 		struct tm tm;
756 		localtime_r(&creationTime, &tm);
757 
758 		size_t length = strftime(date, sizeof(date),
759 			"%a, %d %b %Y %H:%M:%S", &tm);
760 
761 		// GMT offsets are full hours, yes, but you never know :-)
762 		snprintf(date + length, sizeof(date) - length, " %+03d%02d",
763 			tm.tm_gmtoff / 3600, (tm.tm_gmtoff / 60) % 60);
764 
765 		SetHeaderField("Date", date);
766 	}
767 
768 	// add a message-id
769 
770 	// empirical evidence indicates message id must be enclosed in
771 	// angle brackets and there must be an "at" symbol in it
772 	BString messageID;
773 	messageID << "<";
774 	messageID << system_time();
775 	messageID << "-BeMail@";
776 
777 	char host[255];
778 	if (gethostname(host, sizeof(host)) < 0 || !host[0])
779 		strcpy(host, "zoidberg");
780 
781 	messageID << host;
782 	messageID << ">";
783 
784 	SetHeaderField("Message-Id", messageID.String());
785 
786 	status_t err = BMailComponent::RenderToRFC822(file);
787 	if (err < B_OK)
788 		return err;
789 
790 	file->Seek(-2, SEEK_CUR);
791 		// Remove division between headers
792 
793 	err = fBody->RenderToRFC822(file);
794 	if (err < B_OK)
795 		return err;
796 
797 	// Set the message file's attributes.  Do this after the rest of the file
798 	// is filled in, in case the daemon attempts to send it before it is ready
799 	// (since the daemon may send it when it sees the status attribute getting
800 	// set to "Pending").
801 
802 	if (BFile* attributed = dynamic_cast <BFile*>(file)) {
803 		BNodeInfo(attributed).SetType(B_MAIL_TYPE);
804 
805 		attributed->WriteAttrString(B_MAIL_ATTR_RECIPIENTS,&recipients);
806 
807 		BString attr;
808 
809 		attr = To();
810 		attributed->WriteAttrString(B_MAIL_ATTR_TO, &attr);
811 		attr = CC();
812 		attributed->WriteAttrString(B_MAIL_ATTR_CC, &attr);
813 		attr = Subject();
814 		attributed->WriteAttrString(B_MAIL_ATTR_SUBJECT, &attr);
815 		attr = ReplyTo();
816 		attributed->WriteAttrString(B_MAIL_ATTR_REPLY, &attr);
817 		attr = From();
818 		attributed->WriteAttrString(B_MAIL_ATTR_FROM, &attr);
819 		if (Priority() != 3 /* Normal is 3 */) {
820 			sprintf(attr.LockBuffer(40), "%d", Priority());
821 			attr.UnlockBuffer(-1);
822 			attributed->WriteAttrString(B_MAIL_ATTR_PRIORITY, &attr);
823 		}
824 		attr = "Pending";
825 		attributed->WriteAttrString(B_MAIL_ATTR_STATUS, &attr);
826 		attr = "1.0";
827 		attributed->WriteAttrString(B_MAIL_ATTR_MIME, &attr);
828 
829 		attributed->WriteAttr(B_MAIL_ATTR_ACCOUNT, B_INT32_TYPE, 0,
830 			&fAccountID, sizeof(int32));
831 
832 		attributed->WriteAttr(B_MAIL_ATTR_WHEN, B_TIME_TYPE, 0, &creationTime,
833 			sizeof(int32));
834 		int32 flags = B_MAIL_PENDING | B_MAIL_SAVE;
835 		attributed->WriteAttr(B_MAIL_ATTR_FLAGS, B_INT32_TYPE, 0, &flags,
836 			sizeof(int32));
837 
838 		attributed->WriteAttr(B_MAIL_ATTR_ACCOUNT_ID, B_INT32_TYPE, 0,
839 			&fAccountID, sizeof(int32));
840 	}
841 
842 	return B_OK;
843 }
844 
845 
846 status_t
847 BEmailMessage::RenderTo(BDirectory* dir, BEntry* msg)
848 {
849 	time_t currentTime;
850 	char numericDateString[40];
851 	struct tm timeFields;
852 	BString worker;
853 
854 	// Generate a file name for the outgoing message.  See also
855 	// FolderFilter::ProcessMailMessage which does something similar for
856 	// incoming messages.
857 
858 	BString name = Subject();
859 	SubjectToThread(name);
860 		// Extract the core subject words.
861 	if (name.Length() <= 0)
862 		name = "No Subject";
863 	if (name[0] == '.') {
864 		// Avoid hidden files, starting with a dot.
865 		name.Prepend("_");
866 	}
867 
868 	// Convert the date into a year-month-day fixed digit width format, so that
869 	// sorting by file name will give all the messages with the same subject in
870 	// order of date.
871 	time (&currentTime);
872 	localtime_r (&currentTime, &timeFields);
873 	sprintf (numericDateString, "%04d%02d%02d%02d%02d%02d",
874 		timeFields.tm_year + 1900, timeFields.tm_mon + 1, timeFields.tm_mday,
875 		timeFields.tm_hour, timeFields.tm_min, timeFields.tm_sec);
876 	name << " " << numericDateString;
877 
878 	worker = From();
879 	extract_address_name(worker);
880 	name << " " << worker;
881 
882 	name.Truncate(222);	// reserve space for the uniquer
883 
884 	// Get rid of annoying characters which are hard to use in the shell.
885 	name.ReplaceAll('/','_');
886 	name.ReplaceAll('\'','_');
887 	name.ReplaceAll('"','_');
888 	name.ReplaceAll('!','_');
889 	name.ReplaceAll('<','_');
890 	name.ReplaceAll('>','_');
891 
892 	// Remove multiple spaces.
893 	while (name.FindFirst("  ") >= 0)
894 		name.Replace("  ", " ", 1024);
895 
896 	int32 uniquer = time(NULL);
897 	worker = name;
898 
899 	int32 tries = 30;
900 	bool exists;
901 	while ((exists = dir->Contains(worker.String())) && --tries > 0) {
902 		srand(rand());
903 		uniquer += (rand() >> 16) - 16384;
904 
905 		worker = name;
906 		worker << ' ' << uniquer;
907 	}
908 
909 	if (exists)
910 		printf("could not create mail! (should be: %s)\n", worker.String());
911 
912 	BFile file;
913 	status_t status = dir->CreateFile(worker.String(), &file);
914 	if (status != B_OK)
915 		return status;
916 
917 	if (msg != NULL)
918 		msg->SetTo(dir,worker.String());
919 
920 	return RenderToRFC822(&file);
921 }
922 
923 
924 status_t
925 BEmailMessage::Send(bool sendNow)
926 {
927 	BMailAccounts accounts;
928 	BMailAccountSettings* account = accounts.AccountByID(fAccountID);
929 	if (account == NULL || !account->HasOutbound()) {
930 		account = accounts.AccountByID(
931 			BMailSettings().DefaultOutboundAccount());
932 		if (!account)
933 			return B_ERROR;
934 		SendViaAccount(account->AccountID());
935 	}
936 
937 	BString path;
938 	if (account->OutboundSettings().FindString("path", &path) != B_OK) {
939 		BPath defaultMailOutPath;
940 		if (find_directory(B_USER_DIRECTORY, &defaultMailOutPath) != B_OK
941 			|| defaultMailOutPath.Append("mail/out") != B_OK)
942 			path = "/boot/home/mail/out";
943 		else
944 			path = defaultMailOutPath.Path();
945 	}
946 
947 	create_directory(path.String(), 0777);
948 	BDirectory directory(path.String());
949 
950 	BEntry message;
951 
952 	status_t status = RenderTo(&directory, &message);
953 	if (status >= B_OK && sendNow) {
954 		// TODO: check whether or not the internet connection is available
955 		BMessenger daemon(B_MAIL_DAEMON_SIGNATURE);
956 		if (!daemon.IsValid())
957 			return B_MAIL_NO_DAEMON;
958 
959 		BMessage msg(kMsgSendMessages);
960 		msg.AddInt32("account", fAccountID);
961 		BPath path;
962 		message.GetPath(&path);
963 		msg.AddString("message_path", path.Path());
964 		daemon.SendMessage(&msg);
965 	}
966 
967 	return status;
968 }
969 
970 
971 void BEmailMessage::_ReservedMessage1() {}
972 void BEmailMessage::_ReservedMessage2() {}
973 void BEmailMessage::_ReservedMessage3() {}
974