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			// Note: GCC 13 warns about the unused result of the statement below. This code should
415			//       be reviewed as part of #18478
416			#if __GNUC__ == 13
417			#  pragma GCC diagnostic push
418			#  pragma GCC diagnostic warning "-Wunused-result"
419			#endif
420			fPendingBodies[*iterator].begin();
421			#if __GNUC__ == 13
422			#  pragma GCC diagnostic pop
423			#endif
424		}
425	}
426}
427
428
429/*!	Appends the given \a stream as body to the message file for the
430	specified unique ID. The file will remain open when this method exits
431	without an error.
432
433	\a length will reflect how many bytes are left to read in case there
434	were an error.
435*/
436status_t
437IMAPFolder::StoreBody(uint32 uid, BDataIO& stream, size_t& length,
438	entry_ref& ref, BFile& file)
439{
440	status_t status = GetMessageEntryRef(uid, ref);
441	if (status != B_OK)
442		return status;
443
444	status = file.SetTo(&ref, B_OPEN_AT_END | B_WRITE_ONLY);
445	if (status != B_OK)
446		return status;
447
448	BPath path(&ref);
449	printf("IMAP: write body to %s\n", path.Path());
450
451	return _WriteStream(file, stream, length);
452}
453
454
455/*!	Notifies the protocol that a body has been fetched.
456	This method also closes the \a file passed in.
457*/
458void
459IMAPFolder::BodyStored(entry_ref& ref, BFile& file, uint32 uid)
460{
461	BMessage attributes;
462	fProtocol.MessageStored(*this, ref, file, IMAP::kFetchBody, attributes);
463	file.Unset();
464
465	_NotifyStoredBody(ref, uid, B_OK);
466}
467
468
469void
470IMAPFolder::StoringBodyFailed(const entry_ref& ref, uint32 uid, status_t error)
471{
472	_NotifyStoredBody(ref, uid, error);
473}
474
475
476void
477IMAPFolder::DeleteMessage(uint32 uid)
478{
479	// TODO: move message to trash (server side)
480
481	_DeleteLocalMessage(uid);
482}
483
484
485void
486IMAPFolder::MessageReceived(BMessage* message)
487{
488	switch (message->what) {
489		default:
490			BHandler::MessageReceived(message);
491			break;
492	}
493}
494
495
496void
497IMAPFolder::_WaitForFolderState()
498{
499	while (true) {
500		MutexLocker locker(fFolderStateLock);
501		if (fFolderStateInitialized)
502			return;
503	}
504}
505
506
507void
508IMAPFolder::_InitializeFolderState()
509{
510	mutex_lock(&fFolderStateLock);
511
512	fReadFolderStateThread = spawn_thread(&IMAPFolder::_ReadFolderState,
513		"IMAP folder state", B_NORMAL_PRIORITY, this);
514	if (fReadFolderStateThread >= 0)
515		resume_thread(fReadFolderStateThread);
516	else
517		mutex_unlock(&fFolderStateLock);
518}
519
520
521void
522IMAPFolder::_ReadFolderState()
523{
524	BDirectory directory(&fRef);
525	BEntry entry;
526	while (directory.GetNextEntry(&entry) == B_OK) {
527		entry_ref ref;
528		BNode node;
529		if (!entry.IsFile() || entry.GetRef(&ref) != B_OK
530			|| node.SetTo(&entry) != B_OK)
531			continue;
532
533		uint32 uidValidity = _ReadUniqueIDValidity(node);
534		if (uidValidity != fUIDValidity) {
535			// TODO: add file to mailbox
536			continue;
537		}
538		uint32 uid = _ReadUniqueID(node);
539		uint32 flags = _ReadFlags(node);
540
541		MutexLocker locker(fLock);
542		if (fQuitFolderState)
543			return;
544
545		fRefMap.insert(std::make_pair(uid, ref));
546		fFlagsMap.insert(std::make_pair(uid, flags));
547
548//		// TODO: make sure a listener exists at this point!
549//		std::set<uint32>::iterator found = lastUIDs.find(uid);
550//		if (found != lastUIDs.end()) {
551//			// The message is still around
552//			lastUIDs.erase(found);
553//
554//			uint32 flagsFound = MessageFlags(uid);
555//			if (flagsFound != flags) {
556//				// Its flags have changed locally, and need to be updated
557//				fListener->MessageFlagsChanged(_Token(uid), ref,
558//					flagsFound, flags);
559//			}
560//		} else {
561//			// This is a new message
562//			// TODO: the token must be the originating token!
563//			uid = fListener->MessageAdded(_Token(uid), ref);
564//			_WriteUniqueID(node, uid);
565//		}
566//
567	}
568
569	fFolderStateInitialized = true;
570	mutex_unlock(&fFolderStateLock);
571}
572
573
574/*static*/ status_t
575IMAPFolder::_ReadFolderState(void* self)
576{
577	((IMAPFolder*)self)->_ReadFolderState();
578	return B_OK;
579}
580
581
582const MessageToken
583IMAPFolder::_Token(uint32 uid) const
584{
585	MessageToken token;
586	token.mailboxName = fMailboxName;
587	token.uidValidity = fUIDValidity;
588	token.uid = uid;
589
590	return token;
591}
592
593
594void
595IMAPFolder::_NotifyStoredBody(const entry_ref& ref, uint32 uid, status_t status)
596{
597	MutexLocker locker(fLock);
598	MessengerMap::iterator found = fPendingBodies.find(uid);
599	if (found != fPendingBodies.end()) {
600		MessengerList messengers = found->second;
601		fPendingBodies.erase(found);
602		locker.Unlock();
603
604		MessengerList::iterator iterator = messengers.begin();
605		for (; iterator != messengers.end(); iterator++)
606			BInboundMailProtocol::ReplyBodyFetched(*iterator, ref, status);
607	}
608}
609
610
611status_t
612IMAPFolder::_GetMessageEntryRef(uint32 uid, entry_ref& ref) const
613{
614	UIDToRefMap::const_iterator found = fRefMap.find(uid);
615	if (found == fRefMap.end())
616		return !fFolderStateInitialized ? B_TIMED_OUT : B_ENTRY_NOT_FOUND;
617
618	ref = found->second;
619	return B_OK;
620}
621
622
623status_t
624IMAPFolder::_DeleteLocalMessage(uint32 uid)
625{
626	entry_ref ref;
627	status_t status = GetMessageEntryRef(uid, ref);
628	if (status != B_OK)
629		return status;
630
631	fRefMap.erase(uid);
632	fFlagsMap.erase(uid);
633
634	BEntry entry(&ref);
635	return entry.Remove();
636}
637
638
639void
640IMAPFolder::_IMAPToMailFlags(uint32 flags, BMessage& attributes)
641{
642	// TODO: add some utility function for this in libmail.so
643	if ((flags & IMAP::kAnswered) != 0)
644		attributes.AddString(B_MAIL_ATTR_STATUS, "Answered");
645	else if ((flags & IMAP::kFlagged) != 0)
646		attributes.AddString(B_MAIL_ATTR_STATUS, "Starred");
647	else if ((flags & IMAP::kSeen) != 0)
648		attributes.AddString(B_MAIL_ATTR_STATUS, "Read");
649}
650
651
652status_t
653IMAPFolder::_MailToIMAPFlags(BNode& node, uint32& flags)
654{
655	BString mailStatus;
656	status_t status = node.ReadAttrString(B_MAIL_ATTR_STATUS, &mailStatus);
657	if (status != B_OK)
658		return status;
659
660	flags &= ~(IMAP::kAnswered | IMAP::kSeen);
661
662	// TODO: add some utility function for this in libmail.so
663	if (mailStatus == "Answered")
664		flags |= IMAP::kAnswered | IMAP::kSeen;
665	else if (mailStatus == "Read")
666		flags |= IMAP::kSeen;
667	else if (mailStatus == "Starred")
668		flags |= IMAP::kFlagged | IMAP::kSeen;
669
670	return B_OK;
671}
672
673
674void
675IMAPFolder::_TestMessageFlags(uint32 previousFlags, uint32 mailboxFlags,
676	uint32 currentFlags, uint32 testFlag, uint32& nextFlags)
677{
678	if ((previousFlags & testFlag) != (mailboxFlags & testFlag)) {
679		if ((previousFlags & testFlag) == (currentFlags & testFlag)) {
680			// The flags on the mailbox changed, update local flags
681			nextFlags &= ~testFlag;
682			nextFlags |= mailboxFlags & testFlag;
683		} else {
684			// Both flags changed. Since we don't have the means to do
685			// conflict resolution, we use a best effort mechanism
686			nextFlags |= testFlag;
687		}
688		return;
689	}
690
691	// Previous message flags, and mailbox flags are identical, see
692	// if the user changed the flag locally
693	if ((currentFlags & testFlag) != (previousFlags & testFlag)) {
694		// Flag changed, update mailbox
695		nextFlags &= ~testFlag;
696		nextFlags |= currentFlags & testFlag;
697	}
698}
699
700
701uint32
702IMAPFolder::_ReadUniqueID(BNode& node) const
703{
704	// For compatibility we must assume that the UID is stored as a string
705	BString string;
706	if (node.ReadAttrString(kUIDAttribute, &string) != B_OK)
707		return 0;
708
709	return strtoul(string.String(), NULL, 0);
710}
711
712
713status_t
714IMAPFolder::_WriteUniqueID(BNode& node, uint32 uid) const
715{
716	// For compatibility we must assume that the UID is stored as a string
717	BString string;
718	string << uid;
719
720	return node.WriteAttrString(kUIDAttribute, &string);
721}
722
723
724uint32
725IMAPFolder::_ReadUniqueIDValidity(BNode& node) const
726{
727
728	return _ReadUInt32(node, kUIDValidityAttribute);
729}
730
731
732status_t
733IMAPFolder::_WriteUniqueIDValidity(BNode& node) const
734{
735	return _WriteUInt32(node, kUIDValidityAttribute, fUIDValidity);
736}
737
738
739uint32
740IMAPFolder::_ReadFlags(BNode& node) const
741{
742	return _ReadUInt32(node, kFlagsAttribute);
743}
744
745
746status_t
747IMAPFolder::_WriteFlags(BNode& node, uint32 flags) const
748{
749	return _WriteUInt32(node, kFlagsAttribute, flags);
750}
751
752
753uint32
754IMAPFolder::_ReadUInt32(BNode& node, const char* attribute) const
755{
756	uint32 value;
757	ssize_t bytesRead = node.ReadAttr(attribute, B_UINT32_TYPE, 0,
758		&value, sizeof(uint32));
759	if (bytesRead == (ssize_t)sizeof(uint32))
760		return value;
761
762	return 0;
763}
764
765
766status_t
767IMAPFolder::_WriteUInt32(BNode& node, const char* attribute, uint32 value) const
768{
769	ssize_t bytesWritten = node.WriteAttr(attribute, B_UINT32_TYPE, 0,
770		&value, sizeof(uint32));
771	if (bytesWritten == (ssize_t)sizeof(uint32))
772		return B_OK;
773
774	return bytesWritten < 0 ? bytesWritten : B_IO_ERROR;
775}
776
777
778status_t
779IMAPFolder::_WriteStream(BFile& file, BDataIO& stream, size_t& length) const
780{
781	char buffer[65535];
782	while (length > 0) {
783		ssize_t bytesRead = stream.Read(buffer,
784			std::min(sizeof(buffer), length));
785		if (bytesRead < 0)
786			return bytesRead;
787		if (bytesRead <= 0)
788			break;
789
790		length -= bytesRead;
791
792		ssize_t bytesWritten = file.Write(buffer, bytesRead);
793		if (bytesWritten < 0)
794			return bytesWritten;
795		if (bytesWritten != bytesRead)
796			return B_IO_ERROR;
797	}
798
799	return B_OK;
800}
801