summaryrefslogtreecommitdiffstats
path: root/libmail/mbox.C
diff options
context:
space:
mode:
Diffstat (limited to 'libmail/mbox.C')
-rw-r--r--libmail/mbox.C1530
1 files changed, 1530 insertions, 0 deletions
diff --git a/libmail/mbox.C b/libmail/mbox.C
new file mode 100644
index 0000000..167ee25
--- /dev/null
+++ b/libmail/mbox.C
@@ -0,0 +1,1530 @@
+/*
+** Copyright 2002-2004, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+#include "libmail_config.h"
+#include "driver.H"
+#include "file.H"
+#include "misc.H"
+#include "mbox.H"
+#include "mboxopen.H"
+#include "mboxread.H"
+#include "mboxexpunge.H"
+#include "mboxgetmessage.H"
+#include "search.H"
+#include "copymessage.H"
+#include "mboxmultilock.H"
+#include "liblock/config.h"
+#include "liblock/mail.h"
+
+#include "rfc822/rfc822.h"
+#include "rfc2045/rfc2045.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include <signal.h>
+#include <ctype.h>
+
+using namespace std;
+
+/////////////////////////////////////////////////////////////////////////////
+
+LIBMAIL_START
+
+static bool open_inbox(mail::account *&accountRet,
+ mail::account::openInfo &oi,
+ mail::callback &callback,
+ mail::callback::disconnect &disconnectCallback)
+{
+ if (oi.url.substr(0, 6) != "inbox:")
+ return false;
+
+ accountRet=new mail::mbox(true, oi.url.substr(6),
+ callback,
+ disconnectCallback);
+
+ return true;
+}
+
+static bool open_mbox(mail::account *&accountRet,
+ mail::account::openInfo &oi,
+ mail::callback &callback,
+ mail::callback::disconnect &disconnectCallback)
+{
+ if (oi.url.substr(0, 5) != "mbox:")
+ return false;
+
+ accountRet=new mail::mbox(false, oi.url.substr(5),
+ callback,
+ disconnectCallback);
+
+ return true;
+}
+
+static bool mbox_remote(string url, bool &flag)
+{
+ if (url.substr(0, 6) == "inbox:" ||
+ url.substr(0, 5) == "mbox:")
+ {
+ flag=false;
+ return true;
+ }
+
+ return false;
+}
+
+driver inbox_driver= { &open_inbox, &mbox_remote };
+driver mbox_driver= { &open_mbox, &mbox_remote };
+
+LIBMAIL_END
+
+/////////////////////////////////////////////////////////////////////////////
+
+mail::mbox::task::task(mail::mbox &mboxArg)
+ : mboxAccount(mboxArg)
+{
+}
+
+mail::mbox::task::~task()
+{
+}
+
+void mail::mbox::task::done()
+{
+ if (mboxAccount.tasks.front() != this)
+ LIBMAIL_THROW("Assertion failed: mail::mbox::task::done");
+
+ mboxAccount.tasks.pop();
+ delete this;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+mail::mbox::lock::lock()
+ : ll(NULL), fd(-1), readOnlyLock(false)
+{
+}
+
+mail::mbox::lock::lock(string filename)
+ : ll(ll_mail_alloc(filename.c_str())), fd(-1), readOnlyLock(false)
+{
+ if (!ll)
+ LIBMAIL_THROW("Out of memory.");
+}
+
+mail::mbox::lock *mail::mbox::lock::copy()
+{
+ lock *c=new lock();
+
+ if (!c)
+ LIBMAIL_THROW(strerror(errno));
+
+ c->ll=ll;
+ c->fd=fd;
+ c->readOnlyLock=readOnlyLock;
+
+ ll=NULL;
+ fd=-1;
+
+ return c;
+}
+
+mail::mbox::lock::~lock()
+{
+ if (fd >= 0)
+ close(fd);
+ if (ll)
+ ll_mail_free(ll);
+}
+
+bool mail::mbox::lock::operator()(bool readOnly)
+{
+ if (fd >= 0)
+ return true;
+
+ if (!ll)
+ {
+ errno=ENOENT;
+ return false;
+ }
+
+ // Create a dot-lock file, first.
+
+ if (ll_mail_lock(ll) < 0 && !readOnly)
+ return false;
+
+ // Now, open the file.
+
+ fd=readOnly ? ll_mail_open_ro(ll):ll_mail_open(ll);
+ readOnlyLock=readOnly;
+
+ return fd >= 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+
+mail::mbox::TimedTask::TimedTask(mail::mbox &mboxArg,
+ mail::callback &callbackArg,
+ int timeoutArg)
+ : task(mboxArg), timeout(time(NULL) + timeoutArg), nextTry(0),
+ callback(callbackArg)
+{
+}
+
+mail::mbox::TimedTask::~TimedTask()
+{
+}
+
+void mail::mbox::TimedTask::doit(int &timeout)
+{
+ time_t now=time(NULL);
+
+ if (nextTry && now < nextTry)
+ {
+ int nms=(nextTry - now) * 1000;
+
+ if (timeout > nms)
+ timeout=nms;
+ return;
+ }
+
+ timeout=0;
+
+ if (doit())
+ return;
+
+ nextTry = now + 5; // Try again in 5 seconds.
+
+ if (now >= timeout)
+ timedOut();
+}
+
+void mail::mbox::TimedTask::fail(string errmsg)
+{
+ callback.fail(errmsg);
+ done();
+}
+
+void mail::mbox::TimedTask::timedOut()
+{
+ callback.fail("Operation timed out - mail folder in use.");
+ done();
+}
+
+void mail::mbox::TimedTask::disconnected()
+{
+ callback.fail("Operation cancelled.");
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void mail::mbox::resumed()
+{
+}
+
+void mail::mbox::handler(vector<pollfd> &fds, int &timeout)
+{
+ // Our job is to do the first task at hand, as simple as that.
+
+ if (!tasks.empty())
+ tasks.front()->doit(timeout);
+}
+
+void mail::mbox::installTask(task *t)
+{
+ if (t == NULL)
+ LIBMAIL_THROW("Out of memory.");
+
+ try {
+ tasks.push(t);
+ } catch (...) {
+ delete t;
+ LIBMAIL_THROW(LIBMAIL_THROW_EMPTY);
+ }
+}
+
+mail::mbox::mbox(bool magicInboxArg,
+ string folderRoot, mail::callback &callback,
+ mail::callback::disconnect &disconnect_callback)
+ : mail::account(disconnect_callback),
+ calledDisconnected(false),
+ magicInbox(magicInboxArg),
+ inboxFolder("INBOX", *this),
+ hierarchyFolder("", *this),
+ currentFolderReadOnly(false),
+ folderSavedSize(0),
+ folderSavedTimestamp(0),
+ multiLockLock(NULL),
+ folderDirty(false),
+ newMessages(false),
+ cachedMessageRfcp(NULL),
+ cachedMessageFp(NULL),
+ currentFolderCallback(NULL)
+{
+ sigset_t ss;
+
+ // Ignore SIGUSR2 from c-client
+
+ sigemptyset(&ss);
+ sigaddset(&ss, SIGUSR2);
+ sigprocmask(SIG_BLOCK, &ss, NULL);
+
+ const char *m=getenv("MAIL");
+
+ string h=mail::homedir();
+ struct passwd *pw=getpwuid(getuid());
+
+ if (!pw || h.size() == 0)
+ {
+ callback.fail("Cannot find my home directory!");
+ return;
+ }
+
+
+ inboxMailboxPath=h + "/Inbox";
+
+ // Figure out the mail spool directory.
+
+ if (m && *m)
+ inboxSpoolPath=m;
+ else
+ {
+ static const char *spools[]={"/var/spool/mail", "/var/mail",
+ "/usr/spool/mail", "/usr/mail",
+ 0};
+
+ size_t i;
+
+ for (i=0; spools[i]; i++)
+ if (access(spools[i], X_OK) == 0)
+ {
+ inboxSpoolPath=string(spools[i]) + "/"
+ + pw->pw_name;
+ break;
+ }
+
+ if (!spools[i])
+ {
+ callback.fail("Cannot determine your system mailbox location,");
+ return;
+ }
+ }
+
+ if (folderRoot.size() > 0 && folderRoot[0] != '/')
+ folderRoot=h + "/" + folderRoot;
+
+ rootPath=folderRoot;
+
+ // Initialize the top level folder.
+
+ hierarchyFolder.path=folderRoot;
+ hierarchyFolder.name=folder::defaultName(folderRoot);
+
+ // First time through, create the top level folder directory, if
+ // necessary.
+
+ if (magicInboxArg)
+ mkdir(folderRoot.c_str(), 0700);
+
+ struct stat stat_buf;
+
+ if (stat(folderRoot.c_str(), &stat_buf) == 0 &&
+ S_ISDIR(stat_buf.st_mode))
+ {
+ hierarchyFolder.hasMessages(false);
+ hierarchyFolder.hasSubFolders(true);
+ }
+ else
+ {
+ hierarchyFolder.hasMessages(true);
+ hierarchyFolder.hasSubFolders(false);
+ }
+
+ callback.success("Mail folder opened.");
+}
+
+mail::mbox::~mbox()
+{
+ resetFolder();
+
+ while (!tasks.empty())
+ {
+ tasks.front()->disconnected();
+ tasks.front()->done();
+ }
+
+ if (!calledDisconnected)
+ {
+ calledDisconnected=true;
+ disconnect_callback.disconnected("");
+ }
+}
+
+//
+// General cleanup when the current folder is closed.
+//
+
+void mail::mbox::resetFolder()
+{
+ if (multiLockLock)
+ {
+ delete multiLockLock;
+ multiLockLock=NULL;
+ }
+
+ if (cachedMessageRfcp)
+ {
+ rfc2045_free(cachedMessageRfcp);
+ cachedMessageRfcp=NULL;
+ }
+
+ if (cachedMessageFp)
+ {
+ fclose(cachedMessageFp);
+ cachedMessageFp=NULL;
+ }
+
+ cachedMessageUid="";
+
+ folderMessageIndex.clear();
+ uidmap.clear();
+ folderDirty=false;
+ newMessages=false;
+}
+
+void mail::mbox::logout(mail::callback &callback)
+{
+ if (!folderDirty)
+ {
+ callback.success("Mail folder closed.");
+ return;
+ }
+
+ // Something dirty needs to be saved.
+
+ installTask(new ExpungeTask(*this, callback, false, NULL));
+}
+
+void mail::mbox::checkNewMail(class mail::callback &callback)
+{
+ if (currentFolder.size() == 0)
+ {
+ callback.success("OK"); // Nothing's opened.
+ return;
+ }
+
+ installTask(new CheckNewMailTask(*this, currentFolder,
+ callback, NULL));
+}
+
+void mail::mbox::checkNewMail()
+{
+ // The real folder contents have been updated, now compare against
+ // the folder contents seen by the app, and create entries for
+ // new msgs
+
+ set<string> uidSet;
+
+ // Create an index of all existing msgs seen by the app.
+
+ {
+ vector<mail::messageInfo>::iterator b, e;
+
+ b=index.begin();
+ e=index.end();
+
+ while (b != e)
+ uidSet.insert( (*b++).uid );
+ }
+
+ // Step through the real folder index, see what's new.
+
+ {
+ vector<mboxMessageIndex>::iterator b, e;
+
+ b=folderMessageIndex.begin();
+ e=folderMessageIndex.end();
+
+ while (b != e)
+ {
+ mail::messageInfo i=(*b++).tag.getMessageInfo();
+
+ if (uidSet.count(i.uid))
+ continue;
+
+ index.push_back(i);
+ newMessages=true;
+ }
+ }
+}
+
+bool mail::mbox::hasCapability(string capability)
+{
+ if (capability == LIBMAIL_SINGLEFOLDER)
+ return hierarchyFolder.hasMessages();
+
+ return false;
+}
+
+string mail::mbox::getCapability(string name)
+{
+ mail::upper(name);
+
+ if (name == LIBMAIL_SERVERTYPE)
+ {
+ return "mbox";
+ }
+
+ if (name == LIBMAIL_SERVERDESCR)
+ {
+ return "Local mail folder";
+ }
+
+ if (name == LIBMAIL_SINGLEFOLDER)
+ return hierarchyFolder.hasMessages() ? "1":"";
+
+ return "";
+}
+
+mail::folder *mail::mbox::folderFromString(string s)
+{
+ string name="";
+
+ string::iterator b=s.begin(), e=s.end();
+
+ while (b != e)
+ {
+ if (*b == '\\')
+ {
+ b++;
+
+ if (b == e)
+ break;
+ } else if (*b == ':')
+ {
+ b++;
+ break;
+ }
+
+ name += *b++;
+ }
+
+ // If the path component is relative, prepend home dir.
+
+ string path=string(b, e);
+
+ if (path.size() == 0)
+ path=rootPath;
+ else if (path[0] != '/' && path != "INBOX")
+ path=rootPath + "/" + path;
+
+ return new folder(path, *this);
+}
+
+void mail::mbox::readTopLevelFolders(mail::callback::folderList &callback1,
+ class mail::callback &callback2)
+{
+ vector<const mail::folder *> folder_list;
+
+ if (magicInbox)
+ folder_list.push_back( &inboxFolder );
+
+ if (rootPath.size() > 0)
+ folder_list.push_back( &hierarchyFolder );
+
+ callback1.success(folder_list);
+ callback2.success("OK");
+}
+
+void mail::mbox::findFolder(string path,
+ mail::callback::folderList &callback1,
+ mail::callback &callback2)
+{
+ if (path.size() == 0)
+ path=rootPath;
+ else if (path[0] != '/' && path != "INBOX")
+ path=rootPath + "/" + path;
+
+ folder *f=new folder(path, *this);
+
+ if (!f)
+ {
+ callback2.fail(path + ": " + strerror(errno));
+ return;
+ }
+
+ try {
+ vector<const mail::folder *> folder_list;
+
+ folder_list.push_back(f);
+
+ callback1.success(folder_list);
+ callback2.success("OK");
+ delete f;
+ } catch (...) {
+ delete f;
+ LIBMAIL_THROW(LIBMAIL_THROW_EMPTY);
+ }
+}
+
+void mail::mbox::readMessageAttributes(const vector<size_t> &messages,
+ MessageAttributes attributes,
+ mail::callback::message
+ &callback)
+{
+ vector<size_t> messageCpy=messages;
+
+ // ARRIVALDATE can be handled here, for everything else use the
+ // generic methods.
+
+ if (attributes & mail::account::ARRIVALDATE)
+ {
+ attributes &= ~mail::account::ARRIVALDATE;
+
+ vector<size_t>::iterator b=messageCpy.begin(),
+ e=messageCpy.end();
+
+ MONITOR(mail::mbox);
+
+ while (b != e && !DESTROYED())
+ {
+ size_t n= *b++;
+
+ if (n >= index.size())
+ continue;
+
+ string uid=index[n].uid;
+
+ callback
+ .messageArrivalDateCallback(n,
+ uidmap.count(uid)
+ == 0 ? 0:
+ folderMessageIndex
+ [ uidmap.find(uid)
+ ->second]
+ .internalDate);
+ }
+
+ if (DESTROYED() || attributes == 0)
+ {
+ callback.success("OK");
+ return;
+ }
+ }
+
+ MultiLockRelease *mlock=new MultiLockRelease(*this, callback);
+
+ if (!mlock)
+ {
+ callback.fail(strerror(errno));
+ return;
+ }
+
+ try {
+ MultiLockGenericAttributes *mr=
+ new MultiLockGenericAttributes(*this,
+ messageCpy,
+ attributes,
+ *mlock);
+ if (!mr)
+ LIBMAIL_THROW((strerror(errno)));
+
+ try {
+ installTask(new MultiLock( *this, *mr ));
+ } catch (...) {
+ delete mr;
+ }
+ } catch (...) {
+ delete mlock;
+ LIBMAIL_THROW(LIBMAIL_THROW_EMPTY);
+ }
+}
+
+void mail::mbox::readMessageContent(const vector<size_t> &messages,
+ bool peek,
+ enum mail::readMode readType,
+ mail::callback::message &callback)
+{
+ MultiLockRelease *mlock=new MultiLockRelease(*this, callback);
+
+ if (!mlock)
+ {
+ callback.fail(strerror(errno));
+ return;
+ }
+
+ try {
+ MultiLockGenericMessageRead *mr=
+ new MultiLockGenericMessageRead(*this,
+ messages,
+ peek,
+ readType,
+ *mlock);
+ if (!mr)
+ LIBMAIL_THROW((strerror(errno)));
+
+ try {
+ installTask(new MultiLock( *this, *mr ));
+ } catch (...) {
+ delete mr;
+ }
+ } catch (...) {
+ delete mlock;
+ LIBMAIL_THROW(LIBMAIL_THROW_EMPTY);
+ }
+}
+
+void mail::mbox::readMessageContent(size_t messageNum,
+ bool peek,
+ const mail::mimestruct &msginfo,
+ enum mail::readMode readType,
+ mail::callback::message
+ &callback)
+{
+ genericReadMessageContent(this, this, messageNum, peek,
+ msginfo, readType,
+ callback);
+}
+
+void mail::mbox::readMessageContentDecoded(size_t messageNum,
+ bool peek,
+ const mail::mimestruct &msginfo,
+ mail::callback::message
+ &callback)
+{
+ genericReadMessageContentDecoded(this, this, messageNum, peek,
+ msginfo,
+ callback);
+}
+
+size_t mail::mbox::getFolderIndexSize()
+{
+ return index.size();
+}
+
+mail::messageInfo mail::mbox::getFolderIndexInfo(size_t n)
+{
+ return n < index.size() ? index[n]:mail::messageInfo();
+}
+
+void mail::mbox::saveFolderIndexInfo(size_t messageNum,
+ const mail::messageInfo &info,
+ mail::callback &callback)
+{
+ MONITOR(mail::mbox);
+
+ if (messageNum < index.size())
+ {
+ folderDirty=true;
+
+#define DOFLAG(dummy1, field, dummy2) \
+ (index[messageNum].field= info.field)
+
+ LIBMAIL_MSGFLAGS;
+
+#undef DOFLAG
+
+ }
+
+ callback.success(currentFolderReadOnly ?
+ "Folder opened in read-only mode.":
+ "Message updated.");
+
+ if (! DESTROYED() && messageNum < index.size()
+ && currentFolderCallback)
+ currentFolderCallback->messageChanged(messageNum);
+}
+
+void mail::mbox::updateFolderIndexFlags(const vector<size_t> &messages,
+ bool doFlip,
+ bool enableDisable,
+ const mail::messageInfo &flags,
+ mail::callback &callback)
+{
+ vector<size_t>::const_iterator b, e;
+
+ b=messages.begin();
+ e=messages.end();
+
+ size_t n=index.size();
+
+ while (b != e)
+ {
+ size_t i= *b++;
+
+ if (i < n)
+ {
+#define DOFLAG(dummy, field, dummy2) \
+ if (flags.field) \
+ { \
+ index[i].field=\
+ doFlip ? !index[i].field\
+ : enableDisable; \
+ }
+
+ LIBMAIL_MSGFLAGS;
+#undef DOFLAG
+ }
+ }
+
+ folderDirty=true;
+ b=messages.begin();
+ e=messages.end();
+
+ MONITOR(mail::mbox);
+
+ while (!DESTROYED() && b != e)
+ {
+ size_t i= *b++;
+
+ if (i < n && currentFolderCallback)
+ currentFolderCallback->messageChanged(i);
+ }
+
+ callback.success(!DESTROYED() && currentFolderReadOnly ?
+ "Folder opened in read-only mode.":
+ "Message updated.");
+}
+
+void mail::mbox::genericMarkRead(size_t messageNumber)
+{
+ if (messageNumber < index.size() && index[messageNumber].unread)
+ {
+ index[messageNumber].unread=false;
+ folderDirty=true;
+ if (currentFolderCallback)
+ currentFolderCallback->messageChanged(messageNumber);
+ }
+}
+
+void mail::mbox::updateFolderIndexInfo(mail::callback &callback)
+{
+ if (currentFolder.size() == 0)
+ {
+ callback.success("Mail folder updated");
+ return;
+ }
+
+ installTask(new ExpungeTask(*this, callback, true, NULL));
+}
+
+void mail::mbox::getFolderKeywordInfo(size_t messageNumber,
+ set<string> &keywords)
+{
+ keywords.clear();
+
+ if (messageNumber < index.size())
+ {
+ map<string, size_t>::iterator p=
+ uidmap.find(index[messageNumber].uid);
+
+ if (p != uidmap.end())
+ {
+ folderMessageIndex[p->second].tag.getKeywords()
+ .getFlags(keywords);
+ }
+ }
+}
+
+
+void mail::mbox::updateKeywords(const std::vector<size_t> &messages,
+ const std::set<std::string> &keywords,
+ bool setOrChange,
+ // false: set, true: see changeTo
+ bool changeTo,
+ callback &cb)
+{
+ genericUpdateKeywords(messages, keywords,
+ setOrChange, changeTo, currentFolderCallback,
+ this, cb);
+}
+
+bool mail::mbox::genericProcessKeyword(size_t messageNumber,
+ generic::updateKeywordHelper &helper)
+{
+ if (messageNumber < index.size())
+ {
+ map<string, size_t>::iterator p=
+ uidmap.find(index[messageNumber].uid);
+
+ if (p != uidmap.end())
+ {
+ folderDirty=true;
+ return helper
+ .doUpdateKeyword(folderMessageIndex[p->second]
+ .tag.getKeywords(),
+ keywordHashtable);
+ }
+ }
+ return true;
+}
+
+void mail::mbox::removeMessages(const std::vector<size_t> &messages,
+ callback &cb)
+{
+ installTask(new ExpungeTask(*this, cb, true, &messages));
+}
+
+void mail::mbox::copyMessagesTo(const vector<size_t> &messages,
+ mail::folder *copyTo,
+ mail::callback &callback)
+{
+ mail::copyMessages::copy(this, messages, copyTo, callback);
+}
+
+void mail::mbox::searchMessages(const class mail::searchParams &searchInfo,
+ mail::searchCallback &callback)
+{
+ mail::searchMessages::search(callback, searchInfo, this);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generic message access implementation
+
+void mail::mbox::genericMessageRead(string uid,
+ size_t messageNumber,
+ bool peek,
+ mail::readMode readType,
+ mail::callback::message &callback)
+{
+ installTask(new GenericReadTask(*this, callback, uid, messageNumber,
+ peek, readType));
+}
+
+bool mail::mbox::verifyUid(string uid, size_t &messageNumber,
+ mail::callback &callback)
+{
+ if (uidmap.count(uid) > 0)
+ {
+ if (messageNumber < index.size() &&
+ index[messageNumber].uid == uid)
+ return true;
+
+ size_t n=index.size();
+
+ while (n)
+ {
+ if (index[--n].uid == uid)
+ {
+ messageNumber=n;
+ return true;
+ }
+ }
+ }
+ callback.fail("Message no longer exists in the folder.");
+ return false;
+}
+
+void mail::mbox::genericMessageSize(string uid,
+ size_t messageNumber,
+ mail::callback::message &callback)
+{
+ if (!verifyUid(uid, messageNumber, callback))
+ return;
+
+ size_t n=uidmap.find(uid)->second;
+
+ off_t s=folderMessageIndex[n].startingPos;
+
+ off_t e=n + 1 >= folderMessageIndex.size()
+ ? folderSavedSize:folderMessageIndex[n+1].startingPos;
+
+ callback.messageSizeCallback( messageNumber, e > s ? e-s:0);
+ callback.success("OK");
+}
+
+void mail::mbox::genericGetMessageFd(string uid,
+ size_t messageNumber,
+ bool peek,
+ int &fdRet,
+ mail::callback &callback)
+{
+ if (uid == cachedMessageUid && cachedMessageFp)
+ {
+ fdRet=fileno(cachedMessageFp);
+ callback.success("OK");
+ return;
+ }
+
+ installTask(new GenericGetMessageTask(*this, callback,
+ uid, messageNumber,
+ peek,
+ &fdRet, NULL));
+}
+
+void mail::mbox::genericGetMessageStruct(string uid,
+ size_t messageNumber,
+ struct rfc2045 *&structRet,
+ mail::callback &callback)
+{
+ if (uid == cachedMessageUid && cachedMessageRfcp)
+ {
+ structRet=cachedMessageRfcp;
+ callback.success("OK");
+ return;
+ }
+
+ installTask(new GenericGetMessageTask(*this, callback,
+ uid, messageNumber,
+ true,
+ NULL, &structRet));
+}
+
+void mail::mbox::genericGetMessageFdStruct(string uid,
+ size_t messageNumber,
+ bool peek,
+ int &fdRet,
+ struct rfc2045 *&structret,
+ mail::callback &callback)
+{
+ if (uid == cachedMessageUid && cachedMessageRfcp &&
+ cachedMessageFp)
+ {
+ structret=cachedMessageRfcp;
+ fdRet=fileno(cachedMessageFp);
+ callback.success("OK");
+ return;
+ }
+
+ installTask(new GenericGetMessageTask(*this, callback,
+ uid, messageNumber,
+ peek,
+ &fdRet, &structret));
+}
+
+bool mail::mbox::genericCachedUid(string uid)
+{
+ return uid == cachedMessageUid && cachedMessageRfcp;
+}
+
+/*
+** Hack away at ctime ("Wed Sep 01 13:58:06 2002")
+** in the From_ header until we end up with a timestamp
+*/
+
+static time_t fromCtime(string hdr)
+{
+ char mon[4];
+ int dom, h, m, s, y;
+ char tempbuf[100];
+
+ struct tm *tmptr;
+ time_t tv;
+ int mnum;
+
+ const char *p=hdr.c_str();
+
+ while (*p && !unicode_isspace((unsigned char)*p))
+ p++;
+
+ p++;
+
+ while (*p && !unicode_isspace((unsigned char)*p))
+ p++;
+
+
+ if (sscanf(p, "%*s %3s %d %d:%d:%d %d", mon, &dom,
+ &h, &m, &s, &y)!=6)
+ return 0;
+
+ /* Some hackery to get the month number */
+
+ sprintf(tempbuf, "15 %s %d 12:00:00", mon, y);
+
+ tv=rfc822_parsedt(tempbuf);
+
+ if (tv == 0)
+ return 0;
+
+ tmptr=localtime(&tv);
+ mnum=tmptr->tm_mon;
+
+ /* For real, this time */
+
+ sprintf(tempbuf, "%d %s %d 00:00:00", dom, mon, y);
+
+ tv=rfc822_parsedt(tempbuf);
+
+ if (tv == 0)
+ return 0;
+
+ tmptr=localtime(&tv);
+
+ while ((tmptr=localtime(&tv))->tm_year + 1900 < y ||
+ (tmptr->tm_year + 1900 == y && tmptr->tm_mon < mnum) ||
+ (tmptr->tm_year + 1900 == y && tmptr->tm_mon == mnum &&
+ tmptr->tm_mday < dom))
+ tv += 60 * 60;
+
+ while ((tmptr=localtime(&tv))->tm_year + 1900 > y ||
+ (tmptr->tm_year + 1900 == y && tmptr->tm_mon > mnum) ||
+ (tmptr->tm_year + 1900 == y && tmptr->tm_mon == mnum &&
+ tmptr->tm_mday > dom))
+ tv -= 60 * 60;
+
+ while ((tmptr=localtime(&tv))->tm_mday == dom &&
+ tmptr->tm_hour < h)
+ tv += 60 * 60;
+
+ while ((tmptr=localtime(&tv))->tm_mday == dom &&
+ tmptr->tm_hour > h)
+ tv -= 60 * 60;
+
+ tv += m * 60 + s;
+
+ return tv;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// scan() is where all the exciting stuff happens. scan() reads a file with
+// messages; potentially copies the messages to another file.
+//
+// scan() is invoked in the following situations, where it acts as follows:
+//
+// A. OPENING A NEW FOLDER
+//
+// saveFile=NULL, reopening=false, deleteMsgs=NULL, rewriting=false
+//
+// folderMessageIndex and uidmap are created based on the messages in
+// the file. folderDirty is set to true if there are messages in the
+// file without an existing UID (in which case each message is assigned
+// a new UID).
+//
+// B. NEW MAIL CHECK FROM SYSTEM MAILBOX
+//
+// saveFile=not NULL, reopening=false, deleteMsgs=NULL, rewriting=false
+//
+// Previously, scan() was called in situation A, to open $HOME/Inbox.
+// This time, saveFile is $HOME/Inbox, and scanFile is the existing file
+// is the spoolfile (/var/spool/something, usually).
+//
+// If any messages are found in the spoolfile, they are copied to saveFile,
+// and are added to folderMessageIndex and uidmap. Any existing UIDs in
+// the new messages are ignored, each new message is assigned a new UID,
+// and folderDirty is set to true.
+//
+// C. REOPENING A FOLDER
+//
+// saveFile=NULL, reopening=true, deleteMsgs=NULL, rewriting=false
+//
+// If we believe that the file has not been touched by another process
+// (timestamp and size have not been changed), everything is left alone
+// the way it is. Otherwise;
+//
+// folderMessageIndex and uidmap are created based on the messages in
+// the file. folderDirty is set to true if there are messages in the
+// file without an existing UID (in which case each message is assigned
+// a new UID).
+//
+// D. EXPUNGING THE FOLDER
+//
+// saveFile=new file, reopening=true, deleteMsgs != NULL, rewriting=true
+//
+// Messages are copied to saveFile, except ones that are marked as
+// deleted. folderMessageIndex and uidmap are updated accordingly.
+//
+// E. CLOSING THE FOLDER
+//
+// saveFile=new file, reopening=true, deleteMsgs=NULL, rewriting=true
+//
+// All messages are copied to saveFile, with any updated flags and uids.
+//
+
+bool mail::mbox::scan(mail::file &scanFile,
+ mail::file *saveFile,
+ bool reopening,
+ set<string> *deleteMsgs,
+ bool rewriting,
+ mail::callback *progress)
+{
+ struct stat stat_buf;
+
+ set<string> deleted; // All deleted UIDs
+
+ map<string, mail::messageInfo *> updatedFlags;
+ // List of all updated flags
+
+ vector<mboxMessageIndex> newMessageIndex;
+ // All new UIDs that were created for messages without UIDs.
+
+ if (reopening)
+ {
+ // Initialize the list of all updated message flags,
+ // as well as the deleted messages that should not be copied
+
+ vector<mail::messageInfo>::iterator b=index.begin(),
+ e=index.end();
+
+ while (b != e)
+ {
+ mail::messageInfo &i=*b++;
+
+ if (deleteMsgs != NULL)
+ {
+ if (deleteMsgs->count(i.uid) > 0)
+ deleted.insert(i.uid);
+ }
+
+ updatedFlags.insert(make_pair(i.uid, &i));
+ }
+ }
+
+ // Read the folder file, line by line.
+ // Keep track of the starting points of each message.
+
+
+ bool skipping=false; // Not copying the current message
+
+ bool copying=false; // Message copy in progress
+
+ bool seenFrom=false; // Previous line was a From_ line.
+
+ string fromhdr;
+
+ stat_buf.st_size=0;
+
+ off_t nextUpdatePos=0;
+
+ off_t fromPos=0;
+ size_t rewriteIndex=0;
+
+ string fromLine;
+
+ scanFile.seeked();
+
+ while (!feof(scanFile))
+ {
+ off_t pos; // Current line starts here
+
+ if ((pos=scanFile.tell()) < 0)
+ {
+ return false;
+ }
+
+ // Every 10k bytes send an update.
+
+ if (progress && pos >= nextUpdatePos)
+ {
+ nextUpdatePos=pos + 10000;
+ progress->reportProgress(pos, stat_buf.st_size, 0, 1);
+ }
+
+ string line=scanFile.getline();
+
+ if (strncmp(line.c_str(), "From ", 5) == 0)
+ {
+ fromhdr=line;
+
+ // copying is false if this is the first message.
+
+ if (!copying &&
+ saveFile) // Copying messages.
+ {
+ // We're copying messages, and this is the
+ // first message. A couple of things must
+ // be done:
+
+ // Remember how big the saveFile was,
+ // originally.
+
+ if (fstat(fileno(static_cast<FILE *>
+ (*saveFile)),
+ // fileno may be a macro
+ &stat_buf) < 0)
+ {
+ return false;
+ }
+
+ // Make sure From of the first new
+ // message begins on a new line
+
+ if (stat_buf.st_size > 0)
+ {
+ if (fseek(*saveFile, -1L, SEEK_END)
+ < 0)
+ {
+ return false;
+ }
+
+ int c=getc(*saveFile);
+
+ if (fseek(*saveFile, 0L, SEEK_END) < 0)
+ {
+ return false;
+ }
+
+ if (c != '\n')
+ {
+ stat_buf.st_size++;
+ putc('\n', *saveFile);
+ }
+ }
+ }
+
+ if (!copying && // First message
+
+ !saveFile && reopening && !rewriting) // REOPENING
+ {
+ // Potential short cut.
+
+ if (fstat(fileno(static_cast<FILE *>(scanFile)),
+ &stat_buf) < 0)
+ {
+ return false;
+ }
+
+ if (stat_buf.st_size == folderSavedSize &&
+ stat_buf.st_mtime ==
+ folderSavedTimestamp)
+ {
+ return true;
+ }
+ }
+
+ if (!copying && // First message
+ !saveFile) // NOT READING NEW MAIL
+ resetFolder();
+
+ copying=true;
+ fromLine=line;
+
+ if (saveFile)
+ {
+ if ((pos=ftell(*saveFile)) < 0)
+ {
+ return false;
+ }
+ }
+
+ fromPos=pos;
+ seenFrom=true;
+ continue;
+ }
+
+ if (seenFrom) // Previous line was the From_ line.
+ {
+ seenFrom=false;
+
+ skipping=false;
+
+ // Does the first line of the message has our magic
+ // tag?
+
+ mail::mboxMagicTag tag(line, keywordHashtable);
+
+ if (tag.good()) // Yes.
+ {
+ string uid=tag.getMessageInfo().uid;
+
+ if (deleted.count(uid) > 0)
+ // This one's deleted.
+ {
+ skipping=true;
+ rewriteIndex++;
+ continue;
+ }
+
+ if (saveFile)
+ // Flags in the file might be stale,
+ // make sure the current flags are
+ // written out later.
+ {
+ if (updatedFlags.count(uid) > 0)
+ {
+ mail::messageInfo *i=
+ updatedFlags
+ .find(uid)->second;
+
+ map<string, size_t>
+ ::iterator kw=
+ uidmap.find(uid);
+
+ tag=mail::mboxMagicTag(uid,
+ *i,
+ kw ==
+ uidmap.end() ?
+ tag.getKeywords():
+ folderMessageIndex[kw->second].tag.getKeywords());
+ }
+ else
+ // READING NEW MAIL -- ignore
+ // existing tags.
+
+ tag=mail::mboxMagicTag();
+
+ fprintf(*saveFile, "%s\n%s\n",
+ fromLine.c_str(),
+ tag.toString().c_str());
+ }
+ }
+ else if (rewriteIndex < folderMessageIndex.size() &&
+ deleted.count(folderMessageIndex[rewriteIndex]
+ .tag.getMessageInfo().uid) > 0)
+ {
+ // The folder was opened, this was a new
+ // message without a tag. Subsequently the
+ // message was marked deleted. We can assume
+ // that folderMessageIndex is valid at this
+ // point in time because the mailbox file is
+ // locked.
+
+ skipping=true;
+ rewriteIndex++;
+ continue;
+ }
+ else
+ {
+ folderDirty=true;
+
+ if (rewriting &&
+ rewriteIndex < folderMessageIndex.size())
+ {
+ // See the previous comment, except
+ // for the part where the message was
+ // marked as deleted.
+
+ tag=folderMessageIndex[rewriteIndex]
+ .tag;
+
+ string uid=tag.getMessageInfo().uid;
+
+ if (updatedFlags.count(uid) > 0)
+ {
+ mail::messageInfo *i=
+ updatedFlags
+ .find(uid)->second;
+
+ tag=mail::mboxMagicTag(uid,
+ *i,
+ tag.getKeywords());
+ }
+ }
+ else // Yes, it's really a new message.
+
+ tag=mail::mboxMagicTag();
+
+ if (saveFile)
+ {
+ fprintf(*saveFile, "%s\n%s\n%s\n",
+ fromLine.c_str(),
+ tag.toString().c_str(),
+ line.c_str());
+ }
+ }
+
+ rewriteIndex++;
+
+ mail::messageInfo info=tag.getMessageInfo();
+
+
+ mboxMessageIndex newIndex;
+
+ newIndex.startingPos=fromPos;
+ newIndex.tag=tag;
+ if ((newIndex.internalDate=fromCtime(fromhdr)) == 0)
+ newIndex.internalDate=time(NULL);
+
+ newMessageIndex.push_back(newIndex);
+ }
+ else // Message contents
+ {
+ if (!copying)
+ {
+ // If we get here, this is the first
+ // non-empty line in the file, and it is
+ // not a From_ line.
+
+ if (line.size() == 0)
+ continue;
+
+ errno=EINVAL;
+ return false;
+ }
+
+ if (line.size() == 0 && feof(scanFile))
+ break;
+
+ if (!skipping && saveFile)
+ fprintf(*saveFile, "%s\n", line.c_str());
+ }
+ }
+
+ // Ok, we've created newMessageIndex, and we have an existing index
+ // what now?
+ //
+ // Blow away the existing index if:
+ // 1. Rewriting or closing the folder
+ // 2. If we're opening or reopening the folder, an the file did
+ // not have any messages (resetFolder() was never called inside
+ // the loop - see above).
+ //
+ if ((!copying && !saveFile) || rewriting)
+ resetFolder();
+
+ // At this point now we'll append newMessageIndex to whatever's in
+ // folderMessageIndex, and update uidmap accordingly.
+
+ vector<mboxMessageIndex>::iterator b, e;
+
+ b=newMessageIndex.begin();
+ e=newMessageIndex.end();
+
+ size_t n=folderMessageIndex.size();
+
+ while (b != e)
+ {
+ mboxMessageIndex &i= *b++;
+
+ mail::messageInfo info=i.tag.getMessageInfo();
+
+ if (uidmap.count(info.uid) > 0) // SHOULD NOT HAPPEN
+ {
+ info.uid=mail::mboxMagicTag().getMessageInfo().uid;
+ i.tag=mail::mboxMagicTag(info.uid, info,
+ i.tag.getKeywords());
+ folderDirty=true;
+ }
+
+ folderMessageIndex.insert(folderMessageIndex.end(), i);
+
+ try {
+ uidmap.insert(make_pair(info.uid, n));
+ } catch (...) {
+ folderMessageIndex.erase(folderMessageIndex.begin()+n);
+ LIBMAIL_THROW(LIBMAIL_THROW_EMPTY);
+ }
+ n++;
+ }
+
+ // Cleanup
+
+ if (progress)
+ progress->reportProgress(stat_buf.st_size, stat_buf.st_size,
+ 0, 1);
+
+ if (saveFile && (ferror(*saveFile) || fflush(*saveFile) < 0))
+ {
+ return false;
+ }
+
+ if (ferror(scanFile))
+ {
+ return false;
+ }
+
+ return true;
+}
+
+string mail::mbox::translatePath(string path)
+{
+ return translatePathCommon(path, "./~:", "/");
+}