xref: /haiku/src/kits/mail/MailMessage.cpp (revision 02354704729d38c3b078c696adc1bbbd33cbcf72)
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() 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 (&currentTime);
876 	localtime_r (&currentTime, &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