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