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