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