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