1/*
2 * Copyright 2012-2016, Axel D��rfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "IMAPFolder.h"
8
9#include <set>
10
11#include <ByteOrder.h>
12#include <Debug.h>
13#include <Directory.h>
14#include <File.h>
15#include <fs_attr.h>
16#include <Messenger.h>
17#include <Node.h>
18#include <Path.h>
19
20#include <NodeMessage.h>
21
22#include "IMAPProtocol.h"
23
24
25static const char* kMailboxNameAttribute = "IMAP:mailbox";
26static const char* kUIDValidityAttribute = "IMAP:uidvalidity";
27static const char* kLastUIDAttribute = "IMAP:lastuid";
28static const char* kStateAttribute = "IMAP:state";
29static const char* kFlagsAttribute = "IMAP:flags";
30static const char* kUIDAttribute = "MAIL:unique_id";
31
32
33class TemporaryFile : public BFile {
34public:
35	TemporaryFile(BFile& file)
36		:
37		fFile(file),
38		fDeleteFile(false)
39	{
40	}
41
42	~TemporaryFile()
43	{
44		if (fDeleteFile) {
45			fFile.Unset();
46			BEntry(fPath.Path()).Remove();
47		}
48	}
49
50	status_t Init(const BPath& path, entry_ref& ref)
51	{
52		int32 tries = 53;
53		while (true) {
54			BString name("temp-mail-");
55			name << system_time();
56
57			fPath = path;
58			fPath.Append(name.String());
59
60			status_t status = fFile.SetTo(fPath.Path(),
61				B_CREATE_FILE | B_FAIL_IF_EXISTS | B_READ_WRITE);
62			if (status == B_FILE_EXISTS && tries-- > 0)
63				continue;
64			if (status != B_OK)
65				return status;
66
67			fDeleteFile = true;
68			return get_ref_for_path(fPath.Path(), &ref);
69		}
70	}
71
72	void KeepFile()
73	{
74		fDeleteFile = false;
75	}
76
77private:
78			BFile&				fFile;
79			BPath				fPath;
80			bool				fDeleteFile;
81};
82
83
84// #pragma mark -
85
86
87IMAPFolder::IMAPFolder(IMAPProtocol& protocol, const BString& mailboxName,
88	const entry_ref& ref)
89	:
90	BHandler(mailboxName.String()),
91	fProtocol(protocol),
92	fRef(ref),
93	fMailboxName(mailboxName),
94	fUIDValidity(UINT32_MAX),
95	fLastUID(0),
96	fListener(NULL),
97	fFolderStateInitialized(false),
98	fQuitFolderState(false)
99{
100	mutex_init(&fLock, "imap folder lock");
101	mutex_init(&fFolderStateLock, "imap folder state lock");
102}
103
104
105IMAPFolder::~IMAPFolder()
106{
107	MutexLocker locker(fLock);
108	if (!fFolderStateInitialized && fListener != NULL) {
109		fQuitFolderState = true;
110		locker.Unlock();
111		wait_for_thread(fReadFolderStateThread, NULL);
112	}
113}
114
115
116status_t
117IMAPFolder::Init()
118{
119	// Initialize from folder attributes
120	BNode node(&fRef);
121	status_t status = node.InitCheck();
122	if (status != B_OK)
123		return status;
124
125	node_ref nodeRef;
126	status = node.GetNodeRef(&nodeRef);
127	if (status != B_OK)
128		return status;
129
130	fNodeID = nodeRef.node;
131
132	BString originalMailboxName;
133	if (node.ReadAttrString(kMailboxNameAttribute, &originalMailboxName) == B_OK
134		&& originalMailboxName != fMailboxName) {
135		// TODO: mailbox name has changed
136	}
137
138	fUIDValidity = _ReadUInt32(node, kUIDValidityAttribute);
139	fLastUID = _ReadUInt32(node, kLastUIDAttribute);
140	printf("IMAP: %s, last UID %" B_PRIu32 "\n", fMailboxName.String(),
141		fLastUID);
142
143	attr_info info;
144	status = node.GetAttrInfo(kStateAttribute, &info);
145	if (status == B_OK) {
146		struct entry {
147			uint32	uid;
148			uint32	flags;
149		} _PACKED;
150		struct entry* entries = (struct entry*)malloc(info.size);
151		if (entries == NULL)
152			return B_NO_MEMORY;
153
154		ssize_t bytesRead = node.ReadAttr(kStateAttribute, B_RAW_TYPE, 0,
155			entries, info.size);
156		if (bytesRead != info.size)
157			return B_BAD_DATA;
158
159		for (size_t i = 0; i < info.size / sizeof(entry); i++) {
160			uint32 uid = B_BENDIAN_TO_HOST_INT32(entries[i].uid);
161			uint32 flags = B_BENDIAN_TO_HOST_INT32(entries[i].flags);
162
163			fFlagsMap.insert(std::make_pair(uid, flags));
164		}
165	}
166
167	return B_OK;
168}
169
170
171void
172IMAPFolder::SetListener(FolderListener* listener)
173{
174	ASSERT(fListener == NULL);
175
176	fListener = listener;
177
178	// Initialize current state from actual folder
179	// TODO: this should be done in another thread
180	_InitializeFolderState();
181}
182
183
184void
185IMAPFolder::SetUIDValidity(uint32 uidValidity)
186{
187	if (fUIDValidity == uidValidity)
188		return;
189
190	// TODO: delete all mails that have the same UID validity value we had
191	fUIDValidity = uidValidity;
192
193	BNode node(&fRef);
194	_WriteUInt32(node, kUIDValidityAttribute, uidValidity);
195}
196
197
198status_t
199IMAPFolder::GetMessageEntryRef(uint32 uid, entry_ref& ref)
200{
201	MutexLocker locker(fLock);
202	return _GetMessageEntryRef(uid, ref);
203}
204
205
206status_t
207IMAPFolder::GetMessageUID(const entry_ref& ref, uint32& uid) const
208{
209	BNode node(&ref);
210	status_t status = node.InitCheck();
211	if (status != B_OK)
212		return status;
213
214	uid = _ReadUniqueID(node);
215	if (uid == 0)
216		return B_ENTRY_NOT_FOUND;
217
218	return B_OK;
219}
220
221
222uint32
223IMAPFolder::MessageFlags(uint32 uid)
224{
225	MutexLocker locker(fLock);
226	UIDToFlagsMap::const_iterator found = fFlagsMap.find(uid);
227	if (found == fFlagsMap.end())
228		return 0;
229
230	return found->second;
231}
232
233
234/*!	Synchronizes the message flags/state from the server with the local
235	one.
236*/
237void
238IMAPFolder::SyncMessageFlags(uint32 uid, uint32 mailboxFlags)
239{
240	if (uid > LastUID())
241		return;
242
243	entry_ref ref;
244	BNode node;
245
246	while (true) {
247		status_t status = GetMessageEntryRef(uid, ref);
248		if (status == B_ENTRY_NOT_FOUND) {
249			// The message does not exist anymore locally, delete it on the
250			// server
251			// TODO: copy it to the trash directory first!
252			if (fProtocol.Settings()->DeleteRemoteWhenLocal())
253				fProtocol.UpdateMessageFlags(*this, uid, IMAP::kDeleted);
254			return;
255		}
256		if (status == B_OK)
257			status = node.SetTo(&ref);
258		if (status == B_TIMED_OUT) {
259			// We don't know the message state yet
260			fPendingFlagsMap.insert(std::make_pair(uid, mailboxFlags));
261		}
262		if (status != B_OK)
263			return;
264
265		break;
266	}
267	fSynchronizedUIDsSet.insert(uid);
268
269	uint32 previousFlags = MessageFlags(uid);
270	uint32 currentFlags = previousFlags;
271	if (_MailToIMAPFlags(node, currentFlags) != B_OK)
272		return;
273
274	// Compare flags to previous/current flags, and update either the
275	// message on the server, or the message locally (or even both)
276
277	uint32 nextFlags = mailboxFlags;
278	_TestMessageFlags(previousFlags, mailboxFlags, currentFlags,
279		IMAP::kSeen, nextFlags);
280	_TestMessageFlags(previousFlags, mailboxFlags, currentFlags,
281		IMAP::kAnswered, nextFlags);
282
283	if (nextFlags != previousFlags)
284		_WriteFlags(node, nextFlags);
285	if (currentFlags != nextFlags) {
286		// Update mail message attributes
287		BMessage attributes;
288		_IMAPToMailFlags(nextFlags, attributes);
289		node << attributes;
290
291		fFlagsMap[uid] = nextFlags;
292	}
293	if (mailboxFlags != nextFlags) {
294		// Update server flags
295		fProtocol.UpdateMessageFlags(*this, uid, nextFlags);
296	}
297}
298
299
300void
301IMAPFolder::MessageEntriesFetched()
302{
303	_WaitForFolderState();
304
305	// Synchronize all pending flags first
306	UIDToFlagsMap::const_iterator pendingIterator = fPendingFlagsMap.begin();
307	for (; pendingIterator != fPendingFlagsMap.end(); pendingIterator++)
308		SyncMessageFlags(pendingIterator->first, pendingIterator->second);
309
310	fPendingFlagsMap.clear();
311
312	// Delete all local messages that are no longer found on the server
313
314	MutexLocker locker(fLock);
315	UIDSet deleteUIDs;
316	UIDToRefMap::const_iterator iterator = fRefMap.begin();
317	for (; iterator != fRefMap.end(); iterator++) {
318		uint32 uid = iterator->first;
319		if (fSynchronizedUIDsSet.find(uid) == fSynchronizedUIDsSet.end())
320			deleteUIDs.insert(uid);
321	}
322
323	fSynchronizedUIDsSet.clear();
324	locker.Unlock();
325
326	UIDSet::const_iterator deleteIterator = deleteUIDs.begin();
327	for (; deleteIterator != deleteUIDs.end(); deleteIterator++)
328		_DeleteLocalMessage(*deleteIterator);
329}
330
331
332/*!	Stores the given \a stream into a temporary file using the provided
333	BFile object. A new file will be created, and the \a ref object will
334	point to it. The file will remain open when this method exits without
335	an error.
336
337	\a length will reflect how many bytes are left to read in case there
338	was an error.
339*/
340status_t
341IMAPFolder::StoreMessage(uint32 fetchFlags, BDataIO& stream,
342	size_t& length, entry_ref& ref, BFile& file)
343{
344	BPath path;
345	status_t status = path.SetTo(&fRef);
346	if (status != B_OK)
347		return status;
348
349	TemporaryFile temporaryFile(file);
350	status = temporaryFile.Init(path, ref);
351	if (status != B_OK)
352		return status;
353
354	status = _WriteStream(file, stream, length);
355	if (status == B_OK)
356		temporaryFile.KeepFile();
357
358	return status;
359}
360
361
362/*!	Writes UID, and flags to the message, and notifies the protocol that a
363	message has been fetched. This method also closes the \a file passed in.
364*/
365void
366IMAPFolder::MessageStored(entry_ref& ref, BFile& file, uint32 fetchFlags,
367	uint32 uid, uint32 flags)
368{
369	_WriteUniqueIDValidity(file);
370	_WriteUniqueID(file, uid);
371	if ((fetchFlags & IMAP::kFetchFlags) != 0)
372		_WriteFlags(file, flags);
373
374	BMessage attributes;
375	_IMAPToMailFlags(flags, attributes);
376
377	fProtocol.MessageStored(*this, ref, file, fetchFlags, attributes);
378	file.Unset();
379
380	fRefMap.insert(std::make_pair(uid, ref));
381
382	if (uid > fLastUID) {
383		// Update last known UID
384		fLastUID = uid;
385
386		BNode directory(&fRef);
387		status_t status = _WriteUInt32(directory, kLastUIDAttribute, uid);
388		if (status != B_OK) {
389			fprintf(stderr, "IMAP: Could not write last UID for mailbox "
390				"%s: %s\n", fMailboxName.String(), strerror(status));
391		}
392	}
393}
394
395
396/*!	Pushes the refs for the pending bodies to the pending bodies list.
397	This allows to prevent retrieving bodies more than once.
398*/
399void
400IMAPFolder::RegisterPendingBodies(IMAP::MessageUIDList& uids,
401	const BMessenger* replyTo)
402{
403	MutexLocker locker(fLock);
404
405	MessengerList messengers;
406	if (replyTo != NULL)
407		messengers.push_back(*replyTo);
408
409	IMAP::MessageUIDList::const_iterator iterator = uids.begin();
410	for (; iterator != uids.end(); iterator++) {
411		if (replyTo != NULL)
412			fPendingBodies[*iterator].push_back(*replyTo);
413		else
414			fPendingBodies[*iterator].begin();
415	}
416}
417
418
419/*!	Appends the given \a stream as body to the message file for the
420	specified unique ID. The file will remain open when this method exits
421	without an error.
422
423	\a length will reflect how many bytes are left to read in case there
424	were an error.
425*/
426status_t
427IMAPFolder::StoreBody(uint32 uid, BDataIO& stream, size_t& length,
428	entry_ref& ref, BFile& file)
429{
430	status_t status = GetMessageEntryRef(uid, ref);
431	if (status != B_OK)
432		return status;
433
434	status = file.SetTo(&ref, B_OPEN_AT_END | B_WRITE_ONLY);
435	if (status != B_OK)
436		return status;
437
438	BPath path(&ref);
439	printf("IMAP: write body to %s\n", path.Path());
440
441	return _WriteStream(file, stream, length);
442}
443
444
445/*!	Notifies the protocol that a body has been fetched.
446	This method also closes the \a file passed in.
447*/
448void
449IMAPFolder::BodyStored(entry_ref& ref, BFile& file, uint32 uid)
450{
451	BMessage attributes;
452	fProtocol.MessageStored(*this, ref, file, IMAP::kFetchBody, attributes);
453	file.Unset();
454
455	_NotifyStoredBody(ref, uid, B_OK);
456}
457
458
459void
460IMAPFolder::StoringBodyFailed(const entry_ref& ref, uint32 uid, status_t error)
461{
462	_NotifyStoredBody(ref, uid, error);
463}
464
465
466void
467IMAPFolder::DeleteMessage(uint32 uid)
468{
469	// TODO: move message to trash (server side)
470
471	_DeleteLocalMessage(uid);
472}
473
474
475void
476IMAPFolder::MessageReceived(BMessage* message)
477{
478	switch (message->what) {
479		default:
480			BHandler::MessageReceived(message);
481			break;
482	}
483}
484
485
486void
487IMAPFolder::_WaitForFolderState()
488{
489	while (true) {
490		MutexLocker locker(fFolderStateLock);
491		if (fFolderStateInitialized)
492			return;
493	}
494}
495
496
497void
498IMAPFolder::_InitializeFolderState()
499{
500	mutex_lock(&fFolderStateLock);
501
502	fReadFolderStateThread = spawn_thread(&IMAPFolder::_ReadFolderState,
503		"IMAP folder state", B_NORMAL_PRIORITY, this);
504	if (fReadFolderStateThread >= 0)
505		resume_thread(fReadFolderStateThread);
506	else
507		mutex_unlock(&fFolderStateLock);
508}
509
510
511void
512IMAPFolder::_ReadFolderState()
513{
514	BDirectory directory(&fRef);
515	BEntry entry;
516	while (directory.GetNextEntry(&entry) == B_OK) {
517		entry_ref ref;
518		BNode node;
519		if (!entry.IsFile() || entry.GetRef(&ref) != B_OK
520			|| node.SetTo(&entry) != B_OK)
521			continue;
522
523		uint32 uidValidity = _ReadUniqueIDValidity(node);
524		if (uidValidity != fUIDValidity) {
525			// TODO: add file to mailbox
526			continue;
527		}
528		uint32 uid = _ReadUniqueID(node);
529		uint32 flags = _ReadFlags(node);
530
531		MutexLocker locker(fLock);
532		if (fQuitFolderState)
533			return;
534
535		fRefMap.insert(std::make_pair(uid, ref));
536		fFlagsMap.insert(std::make_pair(uid, flags));
537
538//		// TODO: make sure a listener exists at this point!
539//		std::set<uint32>::iterator found = lastUIDs.find(uid);
540//		if (found != lastUIDs.end()) {
541//			// The message is still around
542//			lastUIDs.erase(found);
543//
544//			uint32 flagsFound = MessageFlags(uid);
545//			if (flagsFound != flags) {
546//				// Its flags have changed locally, and need to be updated
547//				fListener->MessageFlagsChanged(_Token(uid), ref,
548//					flagsFound, flags);
549//			}
550//		} else {
551//			// This is a new message
552//			// TODO: the token must be the originating token!
553//			uid = fListener->MessageAdded(_Token(uid), ref);
554//			_WriteUniqueID(node, uid);
555//		}
556//
557	}
558
559	fFolderStateInitialized = true;
560	mutex_unlock(&fFolderStateLock);
561}
562
563
564/*static*/ status_t
565IMAPFolder::_ReadFolderState(void* self)
566{
567	((IMAPFolder*)self)->_ReadFolderState();
568	return B_OK;
569}
570
571
572const MessageToken
573IMAPFolder::_Token(uint32 uid) const
574{
575	MessageToken token;
576	token.mailboxName = fMailboxName;
577	token.uidValidity = fUIDValidity;
578	token.uid = uid;
579
580	return token;
581}
582
583
584void
585IMAPFolder::_NotifyStoredBody(const entry_ref& ref, uint32 uid, status_t status)
586{
587	MutexLocker locker(fLock);
588	MessengerMap::iterator found = fPendingBodies.find(uid);
589	if (found != fPendingBodies.end()) {
590		MessengerList messengers = found->second;
591		fPendingBodies.erase(found);
592		locker.Unlock();
593
594		MessengerList::iterator iterator = messengers.begin();
595		for (; iterator != messengers.end(); iterator++)
596			BInboundMailProtocol::ReplyBodyFetched(*iterator, ref, status);
597	}
598}
599
600
601status_t
602IMAPFolder::_GetMessageEntryRef(uint32 uid, entry_ref& ref) const
603{
604	UIDToRefMap::const_iterator found = fRefMap.find(uid);
605	if (found == fRefMap.end())
606		return !fFolderStateInitialized ? B_TIMED_OUT : B_ENTRY_NOT_FOUND;
607
608	ref = found->second;
609	return B_OK;
610}
611
612
613status_t
614IMAPFolder::_DeleteLocalMessage(uint32 uid)
615{
616	entry_ref ref;
617	status_t status = GetMessageEntryRef(uid, ref);
618	if (status != B_OK)
619		return status;
620
621	fRefMap.erase(uid);
622	fFlagsMap.erase(uid);
623
624	BEntry entry(&ref);
625	return entry.Remove();
626}
627
628
629void
630IMAPFolder::_IMAPToMailFlags(uint32 flags, BMessage& attributes)
631{
632	// TODO: add some utility function for this in libmail.so
633	if ((flags & IMAP::kAnswered) != 0)
634		attributes.AddString(B_MAIL_ATTR_STATUS, "Answered");
635	else if ((flags & IMAP::kFlagged) != 0)
636		attributes.AddString(B_MAIL_ATTR_STATUS, "Starred");
637	else if ((flags & IMAP::kSeen) != 0)
638		attributes.AddString(B_MAIL_ATTR_STATUS, "Read");
639}
640
641
642status_t
643IMAPFolder::_MailToIMAPFlags(BNode& node, uint32& flags)
644{
645	BString mailStatus;
646	status_t status = node.ReadAttrString(B_MAIL_ATTR_STATUS, &mailStatus);
647	if (status != B_OK)
648		return status;
649
650	flags &= ~(IMAP::kAnswered | IMAP::kSeen);
651
652	// TODO: add some utility function for this in libmail.so
653	if (mailStatus == "Answered")
654		flags |= IMAP::kAnswered | IMAP::kSeen;
655	else if (mailStatus == "Read")
656		flags |= IMAP::kSeen;
657	else if (mailStatus == "Starred")
658		flags |= IMAP::kFlagged | IMAP::kSeen;
659
660	return B_OK;
661}
662
663
664void
665IMAPFolder::_TestMessageFlags(uint32 previousFlags, uint32 mailboxFlags,
666	uint32 currentFlags, uint32 testFlag, uint32& nextFlags)
667{
668	if ((previousFlags & testFlag) != (mailboxFlags & testFlag)) {
669		if ((previousFlags & testFlag) == (currentFlags & testFlag)) {
670			// The flags on the mailbox changed, update local flags
671			nextFlags &= ~testFlag;
672			nextFlags |= mailboxFlags & testFlag;
673		} else {
674			// Both flags changed. Since we don't have the means to do
675			// conflict resolution, we use a best effort mechanism
676			nextFlags |= testFlag;
677		}
678		return;
679	}
680
681	// Previous message flags, and mailbox flags are identical, see
682	// if the user changed the flag locally
683	if ((currentFlags & testFlag) != (previousFlags & testFlag)) {
684		// Flag changed, update mailbox
685		nextFlags &= ~testFlag;
686		nextFlags |= currentFlags & testFlag;
687	}
688}
689
690
691uint32
692IMAPFolder::_ReadUniqueID(BNode& node) const
693{
694	// For compatibility we must assume that the UID is stored as a string
695	BString string;
696	if (node.ReadAttrString(kUIDAttribute, &string) != B_OK)
697		return 0;
698
699	return strtoul(string.String(), NULL, 0);
700}
701
702
703status_t
704IMAPFolder::_WriteUniqueID(BNode& node, uint32 uid) const
705{
706	// For compatibility we must assume that the UID is stored as a string
707	BString string;
708	string << uid;
709
710	return node.WriteAttrString(kUIDAttribute, &string);
711}
712
713
714uint32
715IMAPFolder::_ReadUniqueIDValidity(BNode& node) const
716{
717
718	return _ReadUInt32(node, kUIDValidityAttribute);
719}
720
721
722status_t
723IMAPFolder::_WriteUniqueIDValidity(BNode& node) const
724{
725	return _WriteUInt32(node, kUIDValidityAttribute, fUIDValidity);
726}
727
728
729uint32
730IMAPFolder::_ReadFlags(BNode& node) const
731{
732	return _ReadUInt32(node, kFlagsAttribute);
733}
734
735
736status_t
737IMAPFolder::_WriteFlags(BNode& node, uint32 flags) const
738{
739	return _WriteUInt32(node, kFlagsAttribute, flags);
740}
741
742
743uint32
744IMAPFolder::_ReadUInt32(BNode& node, const char* attribute) const
745{
746	uint32 value;
747	ssize_t bytesRead = node.ReadAttr(attribute, B_UINT32_TYPE, 0,
748		&value, sizeof(uint32));
749	if (bytesRead == (ssize_t)sizeof(uint32))
750		return value;
751
752	return 0;
753}
754
755
756status_t
757IMAPFolder::_WriteUInt32(BNode& node, const char* attribute, uint32 value) const
758{
759	ssize_t bytesWritten = node.WriteAttr(attribute, B_UINT32_TYPE, 0,
760		&value, sizeof(uint32));
761	if (bytesWritten == (ssize_t)sizeof(uint32))
762		return B_OK;
763
764	return bytesWritten < 0 ? bytesWritten : B_IO_ERROR;
765}
766
767
768status_t
769IMAPFolder::_WriteStream(BFile& file, BDataIO& stream, size_t& length) const
770{
771	char buffer[65535];
772	while (length > 0) {
773		ssize_t bytesRead = stream.Read(buffer,
774			std::min(sizeof(buffer), length));
775		if (bytesRead < 0)
776			return bytesRead;
777		if (bytesRead <= 0)
778			break;
779
780		length -= bytesRead;
781
782		ssize_t bytesWritten = file.Write(buffer, bytesRead);
783		if (bytesWritten < 0)
784			return bytesWritten;
785		if (bytesWritten != bytesRead)
786			return B_IO_ERROR;
787	}
788
789	return B_OK;
790}
791