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