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