xref: /haiku/src/kits/mail/MailMessage.cpp (revision 1c09002cbee8e797a0f8bbfc5678dfadd39ee1a7)
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,
640 	bool parse_now)
641 {
642 	if (BFile *file = dynamic_cast<BFile *>(mail_file)) {
643 		file->ReadAttr(B_MAIL_ATTR_ACCOUNT_ID, B_INT32_TYPE, 0, &_account_id,
644 			sizeof(_account_id));
645 	}
646 
647 	mail_file->Seek(0,SEEK_END);
648 	length = mail_file->Position();
649 	mail_file->Seek(0,SEEK_SET);
650 
651 	_status = BMailComponent::SetToRFC822(mail_file,length,parse_now);
652 	if (_status < B_OK)
653 		return _status;
654 
655 	_body = WhatIsThis();
656 
657 	mail_file->Seek(0,SEEK_SET);
658 	_status = _body->SetToRFC822(mail_file,length,parse_now);
659 	if (_status < B_OK)
660 		return _status;
661 
662 	//------------Move headers that we use to us, everything else to _body
663 	const char *name;
664 	for (int32 i = 0; (name = _body->HeaderAt(i)) != NULL; i++) {
665 		if ((strcasecmp(name,"Subject") != 0)
666 		  && (strcasecmp(name,"To") != 0)
667 		  && (strcasecmp(name,"From") != 0)
668 		  && (strcasecmp(name,"Reply-To") != 0)
669 		  && (strcasecmp(name,"Cc") != 0)
670 		  && (strcasecmp(name,"Priority") != 0)
671 		  && (strcasecmp(name,"X-Priority") != 0)
672 		  && (strcasecmp(name,"X-Msmail-Priority") != 0)
673 		  && (strcasecmp(name,"Date") != 0)) {
674 			RemoveHeader(name);
675 		}
676 	}
677 
678 	_body->RemoveHeader("Subject");
679 	_body->RemoveHeader("To");
680 	_body->RemoveHeader("From");
681 	_body->RemoveHeader("Reply-To");
682 	_body->RemoveHeader("Cc");
683 	_body->RemoveHeader("Priority");
684 	_body->RemoveHeader("X-Priority");
685 	_body->RemoveHeader("X-Msmail-Priority");
686 	_body->RemoveHeader("Date");
687 
688 	_num_components = 1;
689 	if (BMIMEMultipartMailContainer *container = dynamic_cast<BMIMEMultipartMailContainer *>(_body))
690 		_num_components = container->CountComponents();
691 
692 	return B_OK;
693 }
694 
695 
696 status_t
697 BEmailMessage::RenderToRFC822(BPositionIO *file)
698 {
699 	if (_body == NULL)
700 		return B_MAIL_INVALID_MAIL;
701 
702 	// Do real rendering
703 
704 	if (From() == NULL) {
705 		// set the "From:" string
706 		SendViaAccount(_account_id);
707 	}
708 
709 	BList recipientList;
710 	get_address_list(recipientList, To(), extract_address);
711 	get_address_list(recipientList, CC(), extract_address);
712 	get_address_list(recipientList, _bcc, extract_address);
713 
714 	BString recipients;
715 	for (int32 i = recipientList.CountItems(); i-- > 0;) {
716 		char *address = (char *)recipientList.RemoveItem(0L);
717 
718 		recipients << '<' << address << '>';
719 		if (i)
720 			recipients << ',';
721 
722 		free(address);
723 	}
724 
725 	// add the date field
726 	int32 creationTime = time(NULL);
727 	{
728 		char date[128];
729 		struct tm tm;
730 		localtime_r(&creationTime, &tm);
731 
732 		size_t length = strftime(date, sizeof(date),
733 			"%a, %d %b %Y %H:%M:%S", &tm);
734 
735 		// GMT offsets are full hours, yes, but you never know :-)
736 		snprintf(date + length, sizeof(date) - length, " %+03d%02d",
737 			tm.tm_gmtoff / 3600, (tm.tm_gmtoff / 60) % 60);
738 
739 		SetHeaderField("Date", date);
740 	}
741 
742 	// add a message-id
743 
744 	// empirical evidence indicates message id must be enclosed in
745 	// angle brackets and there must be an "at" symbol in it
746 	BString messageID;
747 	messageID << "<";
748 	messageID << system_time();
749 	messageID << "-BeMail@";
750 
751 	char host[255];
752 	if (gethostname(host, sizeof(host)) < 0 || !host[0])
753 		strcpy(host, "zoidberg");
754 
755 	messageID << host;
756 	messageID << ">";
757 
758 	SetHeaderField("Message-Id", messageID.String());
759 
760 	status_t err = BMailComponent::RenderToRFC822(file);
761 	if (err < B_OK)
762 		return err;
763 
764 	file->Seek(-2, SEEK_CUR);
765 		// Remove division between headers
766 
767 	err = _body->RenderToRFC822(file);
768 	if (err < B_OK)
769 		return err;
770 
771 	// Set the message file's attributes.  Do this after the rest of the file
772 	// is filled in, in case the daemon attempts to send it before it is ready
773 	// (since the daemon may send it when it sees the status attribute getting
774 	// set to "Pending").
775 
776 	if (BFile *attributed = dynamic_cast <BFile *>(file)) {
777 		BNodeInfo(attributed).SetType(B_MAIL_TYPE);
778 
779 		attributed->WriteAttrString(B_MAIL_ATTR_RECIPIENTS,&recipients);
780 
781 		BString attr;
782 
783 		attr = To();
784 		attributed->WriteAttrString(B_MAIL_ATTR_TO,&attr);
785 		attr = CC();
786 		attributed->WriteAttrString(B_MAIL_ATTR_CC,&attr);
787 		attr = Subject();
788 		attributed->WriteAttrString(B_MAIL_ATTR_SUBJECT,&attr);
789 		attr = ReplyTo();
790 		attributed->WriteAttrString(B_MAIL_ATTR_REPLY,&attr);
791 		attr = From();
792 		attributed->WriteAttrString(B_MAIL_ATTR_FROM,&attr);
793 		if (Priority() != 3 /* Normal is 3 */) {
794 			sprintf (attr.LockBuffer (40), "%d", Priority());
795 			attr.UnlockBuffer(-1);
796 			attributed->WriteAttrString(B_MAIL_ATTR_PRIORITY,&attr);
797 		}
798 		attr = "Pending";
799 		attributed->WriteAttrString(B_MAIL_ATTR_STATUS, &attr);
800 		attr = "1.0";
801 		attributed->WriteAttrString(B_MAIL_ATTR_MIME, &attr);
802 
803 		attributed->WriteAttr(B_MAIL_ATTR_ACCOUNT, B_INT32_TYPE, 0,
804 			&_account_id, sizeof(int32));
805 
806 		attributed->WriteAttr(B_MAIL_ATTR_WHEN, B_TIME_TYPE, 0, &creationTime,
807 			sizeof(int32));
808 		int32 flags = B_MAIL_PENDING | B_MAIL_SAVE;
809 		attributed->WriteAttr(B_MAIL_ATTR_FLAGS, B_INT32_TYPE, 0, &flags,
810 			sizeof(int32));
811 
812 		attributed->WriteAttr(B_MAIL_ATTR_ACCOUNT_ID, B_INT32_TYPE, 0,
813 			&_account_id, sizeof(int32));
814 	}
815 
816 	return B_OK;
817 }
818 
819 
820 status_t
821 BEmailMessage::RenderTo(BDirectory *dir, BEntry *msg)
822 {
823 	time_t		currentTime;
824 	char		numericDateString [40];
825 	struct tm   timeFields;
826 	BString		worker;
827 
828 	// Generate a file name for the outgoing message.  See also
829 	// FolderFilter::ProcessMailMessage which does something similar for
830 	// incoming messages.
831 
832 	BString name = Subject();
833 	SubjectToThread (name); // Extract the core subject words.
834 	if (name.Length() <= 0)
835 		name = "No Subject";
836 	if (name[0] == '.')
837 		name.Prepend ("_"); // Avoid hidden files, starting with a dot.
838 
839 	// Convert the date into a year-month-day fixed digit width format, so that
840 	// sorting by file name will give all the messages with the same subject in
841 	// order of date.
842 	time (&currentTime);
843 	localtime_r (&currentTime, &timeFields);
844 	sprintf (numericDateString, "%04d%02d%02d%02d%02d%02d",
845 		timeFields.tm_year + 1900,
846 		timeFields.tm_mon + 1,
847 		timeFields.tm_mday,
848 		timeFields.tm_hour,
849 		timeFields.tm_min,
850 		timeFields.tm_sec);
851 	name << " " << numericDateString;
852 
853 	worker = From();
854 	extract_address_name(worker);
855 	name << " " << worker;
856 
857 	name.Truncate(222);	// reserve space for the uniquer
858 
859 	// Get rid of annoying characters which are hard to use in the shell.
860 	name.ReplaceAll('/','_');
861 	name.ReplaceAll('\'','_');
862 	name.ReplaceAll('"','_');
863 	name.ReplaceAll('!','_');
864 	name.ReplaceAll('<','_');
865 	name.ReplaceAll('>','_');
866 	while (name.FindFirst("  ") >= 0) // Remove multiple spaces.
867 		name.Replace("  " /* Old */, " " /* New */, 1024 /* Count */);
868 
869 	int32 uniquer = time(NULL);
870 	worker = name;
871 
872 	int32 tries = 30;
873 	bool exists;
874 	while (((exists = dir->Contains(worker.String())) == true) /* pacify mwcc */ &&
875 		(--tries > 0)) {
876 		srand(rand());
877 		uniquer += (rand() >> 16) - 16384;
878 
879 		worker = name;
880 		worker << ' ' << uniquer;
881 	}
882 
883 	if (exists)
884 		printf("could not create mail! (should be: %s)\n", worker.String());
885 
886 	BFile file;
887 	status_t status = dir->CreateFile(worker.String(), &file);
888 	if (status < B_OK)
889 		return status;
890 
891 	if (msg != NULL)
892 		msg->SetTo(dir,worker.String());
893 
894 	return RenderToRFC822(&file);
895 }
896 
897 
898 status_t
899 BEmailMessage::Send(bool send_now)
900 {
901 	BMailAccounts accounts;
902 	BMailAccountSettings* account = accounts.AccountByID(_account_id);
903 	if (!account || !account->HasOutbound()) {
904 		account = accounts.AccountByID(
905 			BMailSettings().DefaultOutboundAccount());
906 		if (!account)
907 			return B_ERROR;
908 		SendViaAccount(account->AccountID());
909 	}
910 
911 	BString path;
912 	if (account->OutboundSettings().Settings().FindString("path", &path)
913 		!= B_OK) {
914 		BPath defaultMailOutPath;
915 		if (find_directory(B_USER_DIRECTORY, &defaultMailOutPath) != B_OK
916 			|| defaultMailOutPath.Append("mail/out") != B_OK)
917 			path = "/boot/home/mail/out";
918 		else
919 			path = defaultMailOutPath.Path();
920 	}
921 
922 	create_directory(path.String(), 0777);
923 	BDirectory directory(path.String());
924 
925 	BEntry message;
926 
927 	status_t status = RenderTo(&directory, &message);
928 	if (status >= B_OK && send_now) {
929 		BMailSettings settings_file;
930 		if (settings_file.SendOnlyIfPPPUp()) {
931 #ifndef HAIKU_TARGET_PLATFORM_BEOS
932 			int s = socket(AF_INET, SOCK_DGRAM, 0);
933 			bsppp_status_t ppp_status;
934 
935 			strcpy(ppp_status.if_name, "ppp0");
936 			if (ioctl(s, BONE_SERIAL_PPP_GET_STATUS, &ppp_status, sizeof(ppp_status)) != 0) {
937 				close(s);
938 				return B_OK;
939 			} else {
940 				if (ppp_status.connection_status != BSPPP_CONNECTED) {
941 					close(s);
942 					return B_OK;
943 				}
944 			}
945 			close(s);
946 #else
947 			if (find_thread("tty_thread") <= 0)
948 				return B_OK;
949 #endif
950 		}
951 
952 		BMessenger daemon("application/x-vnd.Be-POST");
953 		if (!daemon.IsValid())
954 			return B_MAIL_NO_DAEMON;
955 
956 		BMessage msg('msnd');
957 		msg.AddInt32("account",_account_id);
958 		BPath path;
959 		message.GetPath(&path);
960 		msg.AddString("message_path",path.Path());
961 		daemon.SendMessage(&msg);
962 	}
963 
964 	return status;
965 }
966 
967 void BEmailMessage::_ReservedMessage1() {}
968 void BEmailMessage::_ReservedMessage2() {}
969 void BEmailMessage::_ReservedMessage3() {}
970 
971