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