* Copyright 2012-2016, Axel Dörfler, axeld@pinc-software.de.
* Distributed under the terms of the MIT License.
*/
#include "IMAPFolder.h"
#include <set>
#include <ByteOrder.h>
#include <Debug.h>
#include <Directory.h>
#include <File.h>
#include <fs_attr.h>
#include <Messenger.h>
#include <Node.h>
#include <Path.h>
#include <NodeMessage.h>
#include "IMAPProtocol.h"
static const char* kMailboxNameAttribute = "IMAP:mailbox";
static const char* kUIDValidityAttribute = "IMAP:uidvalidity";
static const char* kLastUIDAttribute = "IMAP:lastuid";
static const char* kStateAttribute = "IMAP:state";
static const char* kFlagsAttribute = "IMAP:flags";
static const char* kUIDAttribute = "MAIL:unique_id";
class TemporaryFile : public BFile {
public:
TemporaryFile(BFile& file)
:
fFile(file),
fDeleteFile(false)
{
}
~TemporaryFile()
{
if (fDeleteFile) {
fFile.Unset();
BEntry(fPath.Path()).Remove();
}
}
status_t Init(const BPath& path, entry_ref& ref)
{
int32 tries = 53;
while (true) {
BString name("temp-mail-");
name << system_time();
fPath = path;
fPath.Append(name.String());
status_t status = fFile.SetTo(fPath.Path(),
B_CREATE_FILE | B_FAIL_IF_EXISTS | B_READ_WRITE);
if (status == B_FILE_EXISTS && tries-- > 0)
continue;
if (status != B_OK)
return status;
fDeleteFile = true;
return get_ref_for_path(fPath.Path(), &ref);
}
}
void KeepFile()
{
fDeleteFile = false;
}
private:
BFile& fFile;
BPath fPath;
bool fDeleteFile;
};
IMAPFolder::IMAPFolder(IMAPProtocol& protocol, const BString& mailboxName,
const entry_ref& ref)
:
BHandler(mailboxName.String()),
fProtocol(protocol),
fRef(ref),
fMailboxName(mailboxName),
fUIDValidity(UINT32_MAX),
fLastUID(0),
fListener(NULL),
fFolderStateInitialized(false),
fQuitFolderState(false)
{
mutex_init(&fLock, "imap folder lock");
mutex_init(&fFolderStateLock, "imap folder state lock");
}
IMAPFolder::~IMAPFolder()
{
MutexLocker locker(fLock);
if (!fFolderStateInitialized && fListener != NULL) {
fQuitFolderState = true;
locker.Unlock();
wait_for_thread(fReadFolderStateThread, NULL);
}
}
status_t
IMAPFolder::Init()
{
BNode node(&fRef);
status_t status = node.InitCheck();
if (status != B_OK)
return status;
node_ref nodeRef;
status = node.GetNodeRef(&nodeRef);
if (status != B_OK)
return status;
fNodeID = nodeRef.node;
BString originalMailboxName;
if (node.ReadAttrString(kMailboxNameAttribute, &originalMailboxName) == B_OK
&& originalMailboxName != fMailboxName) {
}
fUIDValidity = _ReadUInt32(node, kUIDValidityAttribute);
fLastUID = _ReadUInt32(node, kLastUIDAttribute);
printf("IMAP: %s, last UID %" B_PRIu32 "\n", fMailboxName.String(),
fLastUID);
attr_info info;
status = node.GetAttrInfo(kStateAttribute, &info);
if (status == B_OK) {
struct entry {
uint32 uid;
uint32 flags;
} _PACKED;
struct entry* entries = (struct entry*)malloc(info.size);
if (entries == NULL)
return B_NO_MEMORY;
ssize_t bytesRead = node.ReadAttr(kStateAttribute, B_RAW_TYPE, 0,
entries, info.size);
if (bytesRead != info.size)
return B_BAD_DATA;
for (size_t i = 0; i < info.size / sizeof(entry); i++) {
uint32 uid = B_BENDIAN_TO_HOST_INT32(entries[i].uid);
uint32 flags = B_BENDIAN_TO_HOST_INT32(entries[i].flags);
fFlagsMap.insert(std::make_pair(uid, flags));
}
}
return B_OK;
}
void
IMAPFolder::SetListener(FolderListener* listener)
{
ASSERT(fListener == NULL);
fListener = listener;
_InitializeFolderState();
}
void
IMAPFolder::SetUIDValidity(uint32 uidValidity)
{
if (fUIDValidity == uidValidity)
return;
fUIDValidity = uidValidity;
BNode node(&fRef);
_WriteUInt32(node, kUIDValidityAttribute, uidValidity);
}
status_t
IMAPFolder::GetMessageEntryRef(uint32 uid, entry_ref& ref)
{
MutexLocker locker(fLock);
return _GetMessageEntryRef(uid, ref);
}
status_t
IMAPFolder::GetMessageUID(const entry_ref& ref, uint32& uid) const
{
BNode node(&ref);
status_t status = node.InitCheck();
if (status != B_OK)
return status;
uid = _ReadUniqueID(node);
if (uid == 0)
return B_ENTRY_NOT_FOUND;
return B_OK;
}
uint32
IMAPFolder::MessageFlags(uint32 uid)
{
MutexLocker locker(fLock);
UIDToFlagsMap::const_iterator found = fFlagsMap.find(uid);
if (found == fFlagsMap.end())
return 0;
return found->second;
}
one.
*/
void
IMAPFolder::SyncMessageFlags(uint32 uid, uint32 mailboxFlags)
{
if (uid > LastUID())
return;
entry_ref ref;
BNode node;
while (true) {
status_t status = GetMessageEntryRef(uid, ref);
if (status == B_ENTRY_NOT_FOUND) {
if (fProtocol.Settings()->DeleteRemoteWhenLocal())
fProtocol.UpdateMessageFlags(*this, uid, IMAP::kDeleted);
return;
}
if (status == B_OK)
status = node.SetTo(&ref);
if (status == B_TIMED_OUT) {
fPendingFlagsMap.insert(std::make_pair(uid, mailboxFlags));
}
if (status != B_OK)
return;
break;
}
fSynchronizedUIDsSet.insert(uid);
uint32 previousFlags = MessageFlags(uid);
uint32 currentFlags = previousFlags;
if (_MailToIMAPFlags(node, currentFlags) != B_OK)
return;
uint32 nextFlags = mailboxFlags;
_TestMessageFlags(previousFlags, mailboxFlags, currentFlags,
IMAP::kSeen, nextFlags);
_TestMessageFlags(previousFlags, mailboxFlags, currentFlags,
IMAP::kAnswered, nextFlags);
if (nextFlags != previousFlags)
_WriteFlags(node, nextFlags);
if (currentFlags != nextFlags) {
BMessage attributes;
_IMAPToMailFlags(nextFlags, attributes);
node << attributes;
fFlagsMap[uid] = nextFlags;
}
if (mailboxFlags != nextFlags) {
fProtocol.UpdateMessageFlags(*this, uid, nextFlags);
}
}
void
IMAPFolder::MessageEntriesFetched()
{
_WaitForFolderState();
UIDToFlagsMap::const_iterator pendingIterator = fPendingFlagsMap.begin();
for (; pendingIterator != fPendingFlagsMap.end(); pendingIterator++)
SyncMessageFlags(pendingIterator->first, pendingIterator->second);
fPendingFlagsMap.clear();
MutexLocker locker(fLock);
UIDSet deleteUIDs;
UIDToRefMap::const_iterator iterator = fRefMap.begin();
for (; iterator != fRefMap.end(); iterator++) {
uint32 uid = iterator->first;
if (fSynchronizedUIDsSet.find(uid) == fSynchronizedUIDsSet.end())
deleteUIDs.insert(uid);
}
fSynchronizedUIDsSet.clear();
locker.Unlock();
UIDSet::const_iterator deleteIterator = deleteUIDs.begin();
for (; deleteIterator != deleteUIDs.end(); deleteIterator++)
_DeleteLocalMessage(*deleteIterator);
}
BFile object. A new file will be created, and the \a ref object will
point to it. The file will remain open when this method exits without
an error.
\a length will reflect how many bytes are left to read in case there
was an error.
*/
status_t
IMAPFolder::StoreMessage(uint32 fetchFlags, BDataIO& stream,
size_t& length, entry_ref& ref, BFile& file)
{
BPath path;
status_t status = path.SetTo(&fRef);
if (status != B_OK)
return status;
TemporaryFile temporaryFile(file);
status = temporaryFile.Init(path, ref);
if (status != B_OK)
return status;
status = _WriteStream(file, stream, length);
if (status == B_OK)
temporaryFile.KeepFile();
return status;
}
message has been fetched. This method also closes the \a file passed in.
*/
void
IMAPFolder::MessageStored(entry_ref& ref, BFile& file, uint32 fetchFlags,
uint32 uid, uint32 flags)
{
_WriteUniqueIDValidity(file);
_WriteUniqueID(file, uid);
if ((fetchFlags & IMAP::kFetchFlags) != 0)
_WriteFlags(file, flags);
BMessage attributes;
_IMAPToMailFlags(flags, attributes);
fProtocol.MessageStored(*this, ref, file, fetchFlags, attributes);
file.Unset();
fRefMap.insert(std::make_pair(uid, ref));
if (uid > fLastUID) {
fLastUID = uid;
BNode directory(&fRef);
status_t status = _WriteUInt32(directory, kLastUIDAttribute, uid);
if (status != B_OK) {
fprintf(stderr, "IMAP: Could not write last UID for mailbox "
"%s: %s\n", fMailboxName.String(), strerror(status));
}
}
}
This allows to prevent retrieving bodies more than once.
*/
void
IMAPFolder::RegisterPendingBodies(IMAP::MessageUIDList& uids,
const BMessenger* replyTo)
{
MutexLocker locker(fLock);
MessengerList messengers;
if (replyTo != NULL)
messengers.push_back(*replyTo);
IMAP::MessageUIDList::const_iterator iterator = uids.begin();
for (; iterator != uids.end(); iterator++) {
if (replyTo != NULL) {
fPendingBodies[*iterator].push_back(*replyTo);
} else {
#if __GNUC__ >= 13
# pragma GCC diagnostic push
# pragma GCC diagnostic warning "-Wunused-result"
#endif
fPendingBodies[*iterator].begin();
#if __GNUC__ >= 13
# pragma GCC diagnostic pop
#endif
}
}
}
specified unique ID. The file will remain open when this method exits
without an error.
\a length will reflect how many bytes are left to read in case there
were an error.
*/
status_t
IMAPFolder::StoreBody(uint32 uid, BDataIO& stream, size_t& length,
entry_ref& ref, BFile& file)
{
status_t status = GetMessageEntryRef(uid, ref);
if (status != B_OK)
return status;
status = file.SetTo(&ref, B_OPEN_AT_END | B_WRITE_ONLY);
if (status != B_OK)
return status;
BPath path(&ref);
printf("IMAP: write body to %s\n", path.Path());
return _WriteStream(file, stream, length);
}
This method also closes the \a file passed in.
*/
void
IMAPFolder::BodyStored(entry_ref& ref, BFile& file, uint32 uid)
{
BMessage attributes;
fProtocol.MessageStored(*this, ref, file, IMAP::kFetchBody, attributes);
file.Unset();
_NotifyStoredBody(ref, uid, B_OK);
}
void
IMAPFolder::StoringBodyFailed(const entry_ref& ref, uint32 uid, status_t error)
{
_NotifyStoredBody(ref, uid, error);
}
void
IMAPFolder::DeleteMessage(uint32 uid)
{
_DeleteLocalMessage(uid);
}
void
IMAPFolder::MessageReceived(BMessage* message)
{
switch (message->what) {
default:
BHandler::MessageReceived(message);
break;
}
}
void
IMAPFolder::_WaitForFolderState()
{
while (true) {
MutexLocker locker(fFolderStateLock);
if (fFolderStateInitialized)
return;
}
}
void
IMAPFolder::_InitializeFolderState()
{
mutex_lock(&fFolderStateLock);
fReadFolderStateThread = spawn_thread(&IMAPFolder::_ReadFolderState,
"IMAP folder state", B_NORMAL_PRIORITY, this);
if (fReadFolderStateThread >= 0)
resume_thread(fReadFolderStateThread);
else
mutex_unlock(&fFolderStateLock);
}
void
IMAPFolder::_ReadFolderState()
{
BDirectory directory(&fRef);
BEntry entry;
while (directory.GetNextEntry(&entry) == B_OK) {
entry_ref ref;
BNode node;
if (!entry.IsFile() || entry.GetRef(&ref) != B_OK
|| node.SetTo(&entry) != B_OK)
continue;
uint32 uidValidity = _ReadUniqueIDValidity(node);
if (uidValidity != fUIDValidity) {
continue;
}
uint32 uid = _ReadUniqueID(node);
uint32 flags = _ReadFlags(node);
MutexLocker locker(fLock);
if (fQuitFolderState)
return;
fRefMap.insert(std::make_pair(uid, ref));
fFlagsMap.insert(std::make_pair(uid, flags));
}
fFolderStateInitialized = true;
mutex_unlock(&fFolderStateLock);
}
status_t
IMAPFolder::_ReadFolderState(void* self)
{
((IMAPFolder*)self)->_ReadFolderState();
return B_OK;
}
const MessageToken
IMAPFolder::_Token(uint32 uid) const
{
MessageToken token;
token.mailboxName = fMailboxName;
token.uidValidity = fUIDValidity;
token.uid = uid;
return token;
}
void
IMAPFolder::_NotifyStoredBody(const entry_ref& ref, uint32 uid, status_t status)
{
MutexLocker locker(fLock);
MessengerMap::iterator found = fPendingBodies.find(uid);
if (found != fPendingBodies.end()) {
MessengerList messengers = found->second;
fPendingBodies.erase(found);
locker.Unlock();
MessengerList::iterator iterator = messengers.begin();
for (; iterator != messengers.end(); iterator++)
BInboundMailProtocol::ReplyBodyFetched(*iterator, ref, status);
}
}
status_t
IMAPFolder::_GetMessageEntryRef(uint32 uid, entry_ref& ref) const
{
UIDToRefMap::const_iterator found = fRefMap.find(uid);
if (found == fRefMap.end())
return !fFolderStateInitialized ? B_TIMED_OUT : B_ENTRY_NOT_FOUND;
ref = found->second;
return B_OK;
}
status_t
IMAPFolder::_DeleteLocalMessage(uint32 uid)
{
entry_ref ref;
status_t status = GetMessageEntryRef(uid, ref);
if (status != B_OK)
return status;
fRefMap.erase(uid);
fFlagsMap.erase(uid);
BEntry entry(&ref);
return entry.Remove();
}
void
IMAPFolder::_IMAPToMailFlags(uint32 flags, BMessage& attributes)
{
if ((flags & IMAP::kAnswered) != 0)
attributes.AddString(B_MAIL_ATTR_STATUS, "Answered");
else if ((flags & IMAP::kFlagged) != 0)
attributes.AddString(B_MAIL_ATTR_STATUS, "Starred");
else if ((flags & IMAP::kSeen) != 0)
attributes.AddString(B_MAIL_ATTR_STATUS, "Read");
}
status_t
IMAPFolder::_MailToIMAPFlags(BNode& node, uint32& flags)
{
BString mailStatus;
status_t status = node.ReadAttrString(B_MAIL_ATTR_STATUS, &mailStatus);
if (status != B_OK)
return status;
flags &= ~(IMAP::kAnswered | IMAP::kSeen);
if (mailStatus == "Answered")
flags |= IMAP::kAnswered | IMAP::kSeen;
else if (mailStatus == "Read")
flags |= IMAP::kSeen;
else if (mailStatus == "Starred")
flags |= IMAP::kFlagged | IMAP::kSeen;
return B_OK;
}
void
IMAPFolder::_TestMessageFlags(uint32 previousFlags, uint32 mailboxFlags,
uint32 currentFlags, uint32 testFlag, uint32& nextFlags)
{
if ((previousFlags & testFlag) != (mailboxFlags & testFlag)) {
if ((previousFlags & testFlag) == (currentFlags & testFlag)) {
nextFlags &= ~testFlag;
nextFlags |= mailboxFlags & testFlag;
} else {
nextFlags |= testFlag;
}
return;
}
if ((currentFlags & testFlag) != (previousFlags & testFlag)) {
nextFlags &= ~testFlag;
nextFlags |= currentFlags & testFlag;
}
}
uint32
IMAPFolder::_ReadUniqueID(BNode& node) const
{
BString string;
if (node.ReadAttrString(kUIDAttribute, &string) != B_OK)
return 0;
return strtoul(string.String(), NULL, 0);
}
status_t
IMAPFolder::_WriteUniqueID(BNode& node, uint32 uid) const
{
BString string;
string << uid;
return node.WriteAttrString(kUIDAttribute, &string);
}
uint32
IMAPFolder::_ReadUniqueIDValidity(BNode& node) const
{
return _ReadUInt32(node, kUIDValidityAttribute);
}
status_t
IMAPFolder::_WriteUniqueIDValidity(BNode& node) const
{
return _WriteUInt32(node, kUIDValidityAttribute, fUIDValidity);
}
uint32
IMAPFolder::_ReadFlags(BNode& node) const
{
return _ReadUInt32(node, kFlagsAttribute);
}
status_t
IMAPFolder::_WriteFlags(BNode& node, uint32 flags) const
{
return _WriteUInt32(node, kFlagsAttribute, flags);
}
uint32
IMAPFolder::_ReadUInt32(BNode& node, const char* attribute) const
{
uint32 value;
ssize_t bytesRead = node.ReadAttr(attribute, B_UINT32_TYPE, 0,
&value, sizeof(uint32));
if (bytesRead == (ssize_t)sizeof(uint32))
return value;
return 0;
}
status_t
IMAPFolder::_WriteUInt32(BNode& node, const char* attribute, uint32 value) const
{
ssize_t bytesWritten = node.WriteAttr(attribute, B_UINT32_TYPE, 0,
&value, sizeof(uint32));
if (bytesWritten == (ssize_t)sizeof(uint32))
return B_OK;
return bytesWritten < 0 ? bytesWritten : B_IO_ERROR;
}
status_t
IMAPFolder::_WriteStream(BFile& file, BDataIO& stream, size_t& length) const
{
char buffer[65535];
while (length > 0) {
ssize_t bytesRead = stream.Read(buffer,
std::min(sizeof(buffer), length));
if (bytesRead < 0)
return bytesRead;
if (bytesRead <= 0)
break;
length -= bytesRead;
ssize_t bytesWritten = file.Write(buffer, bytesRead);
if (bytesWritten < 0)
return bytesWritten;
if (bytesWritten != bytesRead)
return B_IO_ERROR;
}
return B_OK;
}