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