summaryrefslogtreecommitdiffstats
path: root/libmail/smap.C
diff options
context:
space:
mode:
Diffstat (limited to 'libmail/smap.C')
-rw-r--r--libmail/smap.C789
1 files changed, 789 insertions, 0 deletions
diff --git a/libmail/smap.C b/libmail/smap.C
new file mode 100644
index 0000000..e614598
--- /dev/null
+++ b/libmail/smap.C
@@ -0,0 +1,789 @@
+/*
+** Copyright 2003-2008, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+#include "smap.H"
+#include "misc.H"
+#include "imapfolder.H"
+#include "smapnoopexpunge.H"
+#include "smapnewmail.H"
+#include <limits.h>
+#include <iostream>
+#include <sstream>
+#include <errno.h>
+#include "rfc822/rfc822.h"
+#include <cstring>
+
+using namespace std;
+
+mail::smapHandler::smapHandler(int timeoutValArg)
+ : mail::imapHandler(timeoutValArg), defaultCB(NULL)
+{
+}
+
+mail::smapHandler::~smapHandler()
+{
+ fail("Operation aborted."); // Just in case
+}
+
+int mail::smapHandler::process(imap &imapAccount, std::string &buffer)
+{
+ return ( (this->*(imapAccount.smapProtocolHandlerFunc))(imapAccount,
+ buffer));
+}
+
+//
+// Expecting a single line reply.
+//
+
+int mail::smapHandler::singleLineProcess(imap &imapAccount,
+ std::string &buffer)
+{
+ size_t p=buffer.find('\n');
+
+ if (p == std::string::npos)
+ return (0); // Wait until the whole line is read
+
+ setTimeout();
+
+ ++p;
+ string buffer_cpy=buffer;
+ buffer_cpy.erase(p);
+
+ // Parse line into individual words
+
+ vector<const char *> words;
+
+ string::iterator b=buffer_cpy.begin(), e=buffer_cpy.end();
+
+ while (b != e)
+ {
+ if (unicode_isspace((unsigned char)*b))
+ {
+ b++;
+ continue;
+ }
+
+ if (words.size() == 1)
+ {
+ if (strcmp(words[0], "+OK") == 0)
+ {
+ string s=string(b, e);
+
+ while (s.size() > 0 &&
+ unicode_isspace((unsigned char)
+ s.end()[-1]))
+ s.erase(s.end()-1, s.end());
+
+ doDestroy=true;
+ if (ok(fromutf8(s)))
+ {
+ if (doDestroy)
+ imapAccount.
+ uninstallHandler(this);
+ return p;
+ }
+ return 0;
+ }
+
+ if (strcmp(words[0], "-ERR") == 0)
+ {
+ string s=string(b, e);
+
+ while (s.size() > 0 &&
+ unicode_isspace((unsigned char)
+ s.end()[-1]))
+ s.erase(s.end()-1, s.end());
+
+ doDestroy=true;
+
+ if (fail(fromutf8(s)))
+ {
+ if (doDestroy)
+ imapAccount.
+ uninstallHandler(this);
+ return p;
+ }
+ return 0;
+ }
+ }
+
+ words.push_back(&*b);
+
+ if (*b != '"') // Not quoted - look for next space
+ {
+ while (b != e && !unicode_isspace((unsigned char)*b))
+ b++;
+
+ if (b != e)
+ *b++=0;
+ continue;
+ }
+
+ // Extract quoted word.
+
+ string::iterator c=b;
+
+ b++;
+
+ while (b != e)
+ {
+ if (*b == '"')
+ {
+ b++;
+ if (b == e || *b != '"')
+ break;
+ }
+ *c++ = *b++;
+ }
+ *c=0;
+ }
+ if (!processLine(imapAccount, words))
+ return 0;
+
+ return p;
+}
+
+//
+// Reading a dot-stuffed multiline response
+//
+int mail::smapHandler::multiLineProcessDotStuffed(imap &imapAccount,
+ std::string &buffer)
+{
+ size_t p=buffer.find('\n');
+
+ if (p == std::string::npos)
+ return (0); // Also wait until an entire line is read
+
+ setTimeout();
+
+ string buffer_cpy=buffer;
+ buffer_cpy.erase(p);
+ ++p;
+
+ // Swallow trailing CR
+
+ if (buffer_cpy.size() > 0 && buffer_cpy.end()[-1] == '\r')
+ buffer_cpy.erase(buffer_cpy.end()-1, buffer_cpy.end());
+
+ if (buffer_cpy.size() > 0 && buffer_cpy[0] == '.')
+ {
+ buffer_cpy.erase(buffer_cpy.begin(),
+ buffer_cpy.begin()+1);
+ if (buffer_cpy.size() == 0) // Lone dot
+ {
+ imapAccount.smapProtocolHandlerFunc=
+ &smapHandler::singleLineProcess;
+ endData(imapAccount);
+ return p;
+ }
+ }
+
+ processData(imapAccount, buffer_cpy + "\n");
+ return p;
+}
+
+// Reading a multi-line binary response
+
+int mail::smapHandler::multiLineProcessBinary(imap &imapAccount,
+ std::string &buffer)
+{
+ if (imapAccount.smapBinaryCount == 0) // Next chunk
+ {
+ size_t p=buffer.find('\n');
+
+ if (p == std::string::npos)
+ return (0); // Read one line
+
+ setTimeout();
+
+ ++p;
+ string buffer_cpy=buffer;
+ buffer_cpy.erase(p);
+
+ istringstream i(buffer_cpy);
+ unsigned long nextChunk=0;
+
+ i >> nextChunk;
+
+ if (i.fail() || nextChunk == 0) // Empty line. Done.
+ {
+ imapAccount.smapProtocolHandlerFunc=
+ &smapHandler::singleLineProcess;
+ endData(imapAccount);
+ return p;
+ }
+
+ imapAccount.smapBinaryCount=nextChunk;
+ return p;
+ }
+
+ // Ok, some # of bytes to go
+
+ if (imapAccount.smapBinaryCount >= buffer.size())
+ {
+ imapAccount.smapBinaryCount -= buffer.size();
+ setTimeout();
+ processData(imapAccount, buffer);
+ return buffer.size(); // Processed everything
+ }
+
+ string buffer_cpy=buffer;
+
+ buffer_cpy.erase(imapAccount.smapBinaryCount);
+ imapAccount.smapBinaryCount=0;
+ setTimeout();
+ processData(imapAccount, buffer_cpy);
+ return buffer_cpy.size();
+}
+
+void mail::smapHandler::commaSplit(string s, vector<string> &a)
+{
+ while (s.size() > 0)
+ {
+ size_t n=s.find(',');
+
+ if (n == std::string::npos)
+ {
+ a.push_back(s);
+ break;
+ }
+
+ if (n > 0)
+ a.push_back(s.substr(0, n));
+ s=s.substr(n+1);
+ }
+}
+
+//
+// Default single line response handler
+//
+
+bool mail::smapHandler::processLine(imap &imapAccount,
+ std::vector<const char *> &words)
+{
+ if (words.size() == 0)
+ return true;
+
+ const char *p=words[0];
+
+ if (*p == '{') // Start of a multiline response?
+ {
+ if (*++p == '.') // Dot-stuffed
+ {
+ string n(++p);
+ istringstream i(n);
+
+ unsigned long estimate=0;
+
+ i >> estimate;
+
+ words.erase(words.begin(), words.begin()+1);
+
+ imapAccount.smapProtocolHandlerFunc=
+ &smapHandler::multiLineProcessDotStuffed;
+ beginProcessData(imapAccount, words, estimate);
+ return true;
+ }
+
+ // Binary
+
+ string n(p);
+ istringstream i(n);
+ unsigned long firstChunk=0;
+ unsigned long estimate=0;
+ char dummy=0;
+
+ i >> firstChunk >> dummy >> estimate;
+
+ imapAccount.smapBinaryCount=firstChunk;
+ imapAccount.smapProtocolHandlerFunc=
+ &smapHandler::multiLineProcessBinary;
+
+ words.erase(words.begin(), words.begin()+1);
+ beginProcessData(imapAccount, words, estimate);
+ return true;
+ }
+
+ if (words.size() >= 3 && strcmp(words[0], "*") == 0 &&
+ strcasecmp(words[1], "FETCH") == 0)
+ {
+ string n(words[2]);
+ istringstream i(n);
+ size_t msgNum=0;
+
+ i >> msgNum;
+
+ if (!i.fail() && msgNum > 0 &&
+ imapAccount.currentFolder &&
+ msgNum <= imapAccount.currentFolder->index.size())
+ {
+ --msgNum;
+
+ vector<const char *>::iterator b=words.begin()+3,
+ e=words.end();
+
+ for ( ; b != e; b++)
+ {
+ if (strncasecmp( *b, "FLAGS=", 6) == 0)
+ {
+ vector<string> flagList;
+
+ commaSplit( (*b)+6, flagList);
+
+ vector<string>::iterator fb, fe;
+
+ fb=flagList.begin();
+ fe=flagList.end();
+
+ mail::messageInfo newMessageInfo;
+
+ newMessageInfo.uid=
+ imapAccount.currentFolder
+ ->index[msgNum].uid;
+ newMessageInfo.unread=true;
+
+ while (fb != fe)
+ {
+ const char *c= (*fb).c_str();
+
+#define FLAG true
+#define NOTFLAG false
+#define DOFLAG(value, field, name) \
+ if (strcasecmp(c, name) == 0) \
+ newMessageInfo.field=value;
+
+ LIBMAIL_SMAPFLAGS;
+
+ fb++;
+ }
+ (mail::messageInfo &)
+ imapAccount.currentFolder
+ ->index[msgNum]=newMessageInfo;
+
+ if (msgNum < imapAccount.currentFolder
+ ->exists)
+ {
+ messageChanged(msgNum);
+ }
+#if 0
+ cerr << "FLAGS[" << msgNum
+ << "]: deleted="
+ << newMessageInfo.deleted
+ << ", replied="
+ << newMessageInfo.replied
+ << ", unread="
+ << newMessageInfo.unread
+ << ", draft="
+ << newMessageInfo.draft
+ << ", marked="
+ << newMessageInfo.marked
+ << endl;
+#endif
+ fetchedIndexInfo();
+ }
+
+ if (strncasecmp( *b, "KEYWORDS=", 9) == 0)
+ {
+ vector<string> flagList;
+
+ commaSplit( (*b)+9, flagList);
+
+ vector<string>::iterator fb, fe;
+
+ fb=flagList.begin();
+ fe=flagList.end();
+
+ mail::keywords::Message newMessage;
+
+ while (fb != fe)
+ {
+ if (!newMessage
+ .addFlag(imapAccount.
+ keywordHashtable,
+ *fb))
+ LIBMAIL_THROW(strerror(errno));
+ ++fb;
+ }
+
+ imapAccount.currentFolder
+ ->index[msgNum].keywords=
+ newMessage;
+
+ if (msgNum < imapAccount.currentFolder
+ ->exists)
+ {
+ messageChanged(msgNum);
+ }
+ }
+
+ if (strncasecmp( *b, "UID=", 4) == 0)
+ {
+ imapAccount.currentFolder
+ ->index[msgNum].uid= (*b)+4;
+#if 0
+ cerr << "UID[" << msgNum << "]="
+ << imapAccount.currentFolder
+ ->index[msgNum].uid << endl;
+#endif
+ fetchedIndexInfo();
+ }
+
+ if (strncasecmp( *b, "SIZE=", 5) == 0)
+ {
+ unsigned long bytes=0;
+
+ string s= *b + 5;
+ istringstream i(s);
+
+ i >> bytes;
+
+ fetchedMessageSize(msgNum, bytes);
+ }
+
+ if (strncasecmp( *b, "INTERNALDATE=", 13) == 0)
+ {
+ time_t n= rfc822_parsedt(*b + 13);
+
+ if (n)
+ fetchedInternalDate(msgNum, n);
+ }
+ }
+ }
+ return true;
+ }
+
+ if (words.size() >= 3 && strcmp(words[0], "*") == 0 &&
+ strcasecmp(words[1], "EXISTS") == 0)
+ {
+ string n(words[2]);
+ istringstream i(n);
+ size_t msgNum=0;
+
+ i >> msgNum;
+
+ // Check against hostile servers
+
+ if (msgNum > UINT_MAX / sizeof(mail::messageInfo))
+ msgNum=UINT_MAX / sizeof(mail::messageInfo);
+
+ if (!i.fail() && msgNum > 0 &&
+ imapAccount.currentFolder &&
+ !imapAccount.currentFolder->closeInProgress &&
+ msgNum > imapAccount .currentFolder->index.size())
+ {
+ existsOrExpungeSeen();
+ imapAccount.currentFolder->existsMore(imapAccount,
+ msgNum);
+ }
+ return true;
+ }
+
+ if (words.size() >= 2 && strcmp(words[0], "*") == 0 &&
+ strcasecmp(words[1], "EXPUNGE") == 0)
+ {
+ vector< pair<size_t, size_t> > removedList;
+
+ vector<const char *>::iterator b=words.begin()+2,
+ e=words.end();
+
+ while (b != e)
+ {
+ string n(*b++);
+ istringstream i(n);
+
+ size_t first, last;
+ char dummy;
+
+ size_t p=n.find('-');
+
+ if (p != std::string::npos)
+ {
+ i >> first >> dummy >> last;
+ }
+ else
+ {
+ i >> first;
+ dummy='-';
+ last=first;
+ }
+
+ if (i.fail() || dummy != '-' || last < first ||
+ first <= 0 ||
+ (last >= (imapAccount.currentFolder ? (size_t)
+ (imapAccount.currentFolder->index.size()
+ + 1):0))
+ || (removedList.size() > 0 &&
+ first <= removedList.end()[-1].second))
+ continue; // Ignore bogosity
+
+ removedList.push_back(make_pair(first, last));
+ }
+
+ if (removedList.size() == 0)
+ return true;
+
+ vector< pair<size_t, size_t> >::iterator
+ rb=removedList.begin(), re=removedList.end();
+
+ vector<imapFOLDERinfo::indexInfo> &index=
+ imapAccount.currentFolder->index;
+
+ while (rb != re)
+ {
+ --re;
+
+ --re->first;
+ index.erase(index.begin() + re->first,
+ index.begin() + re->second);
+
+ if (imapAccount.currentFolder->exists >= re->first)
+ {
+ if (imapAccount.currentFolder->exists
+ < re->second)
+ imapAccount.currentFolder->exists
+ =re->first;
+ else
+ imapAccount.currentFolder->exists
+ -= re->second - re->first;
+ }
+ --re->second;
+ }
+
+ if (!imapAccount.currentFolder->closeInProgress)
+ existsOrExpungeSeen();
+
+ messagesRemoved(removedList);
+ return true;
+ }
+
+ if (words.size() >= 3 && strcmp(words[0], "*") == 0 &&
+ strcasecmp(words[1], "SNAPSHOT") == 0)
+ {
+ if (imapAccount.currentFolder)
+ imapAccount.currentFolder->
+ folderCallback.saveSnapshot(words[2]);
+
+ return true;
+ }
+
+ return false;
+}
+
+// Default notification handler passes along the expunged/changed list to the
+// application.
+
+void mail::smapHandler::messagesRemoved(vector< pair<size_t, size_t> >
+ &removedList)
+{
+ myimap->currentFolder->folderCallback.messagesRemoved(removedList);
+}
+
+void mail::smapHandler::messageChanged(size_t msgNum)
+{
+ myimap->currentFolder->folderCallback.messageChanged(msgNum);
+}
+
+void mail::smapHandler::existsOrExpungeSeen()
+{
+}
+
+void mail::smapHandler::fetchedMessageSize(size_t msgNum,
+ unsigned long bytes)
+{
+}
+
+void mail::smapHandler::fetchedInternalDate(size_t msgNum,
+ time_t internalDate)
+{
+}
+
+void mail::smapHandler::fetchedIndexInfo()
+{
+}
+
+void mail::smapHandler::beginProcessData(imap &imapAccount,
+ std::vector<const char *> &w,
+ unsigned long estimatedSize)
+{
+}
+
+void mail::smapHandler::processData(imap &imapAccount,
+ std::string data)
+{
+}
+
+void mail::smapHandler::endData(imap &imapAccount)
+{
+}
+
+// Default ok/fail handlers invoke the callback function
+
+bool mail::smapHandler::ok(std::string s)
+{
+ mail::callback *p=defaultCB;
+
+ defaultCB=NULL;
+
+ if (p)
+ p->success(s);
+ return true;
+}
+
+bool mail::smapHandler::fail(std::string s)
+{
+ mail::callback *p=defaultCB;
+
+ defaultCB=NULL;
+
+ if (p)
+ p->fail(s);
+ return true;
+}
+
+void mail::smapHandler::timedOut(const char *errmsg)
+{
+ mail::callback *p=defaultCB;
+
+ defaultCB=NULL;
+
+ if (p)
+ callbackTimedOut(*p, errmsg);
+}
+
+// Convert folder names to a path string
+
+string mail::smapHandler::words2path(vector<const char *> &w)
+{
+ string path="";
+
+ vector<const char *>::iterator b=w.begin(), e=w.end();
+
+ while (b != e)
+ {
+ if (path.size() > 0)
+ path += "/";
+
+ path += mail::iconvert::convert(*b, "utf-8",
+ unicode_x_imap_modutf7 " /");
+
+ b++;
+ }
+
+ return path;
+}
+
+void mail::smapHandler::path2words(string path, vector<string> &words)
+{
+ string::iterator b=path.begin(), e=path.end();
+
+ while (b != e)
+ {
+ string::iterator c=b;
+
+ while (c != e && *c != '/')
+ c++;
+
+ string component=string(b, c);
+
+ b=c;
+ if (b != e)
+ b++;
+
+ words.push_back(mail::iconvert::convert(component,
+ unicode_x_imap_modutf7,
+ "utf-8"));
+ }
+
+ if (words.size() == 0)
+ words.push_back("");
+}
+
+///////////////////////////////////////////////////////////////////////////
+//
+// Currently open folder
+
+mail::smapFOLDER::smapFOLDER(std::string pathArg,
+ mail::callback::folder &folderCallbackArg,
+ mail::imap &myserver)
+ : imapFOLDERinfo(pathArg, folderCallbackArg),
+ imapHandler(myserver.noopSetting), openedFlag(false)
+{
+ mailCheckInterval=myserver.noopSetting;
+}
+
+mail::smapFOLDER::~smapFOLDER()
+{
+}
+
+void mail::smapFOLDER::existsMore(mail::imap &imapAccount, size_t n)
+{
+ size_t o=index.size();
+
+ imapFOLDERinfo::indexInfo newInfo;
+ newInfo.unread=true;
+
+ index.resize(n);
+
+ while (o < n)
+ index[o++]=newInfo;
+
+ myimap->installForegroundTask(new smapNEWMAIL(NULL, false));
+}
+
+void mail::smapFOLDER::opened()
+{
+ openedFlag=true;
+}
+
+void mail::smapFOLDER::resetMailCheckTimer()
+{
+ setTimeout(mailCheckInterval);
+}
+
+int mail::smapFOLDER::getTimeout(mail::imap &imapAccount)
+{
+ int t= imapHandler::getTimeout(imapAccount);
+
+ if (t == 0)
+ {
+ t=MAILCHECKINTERVAL;
+ setTimeout(t);
+
+ imapHandler *h=NULL;
+
+ if (!closeInProgress &&
+ ((h=imapAccount.hasForegroundTask()) == NULL ||
+ strcmp(h->getName(), "IDLE") == 0))
+ {
+ imapAccount
+ .installForegroundTask(new
+ smapNoopExpunge("NOOP",
+ imapAccount)
+ );
+ }
+ }
+
+ return t;
+}
+
+void mail::smapFOLDER::installed(imap &imapAccount)
+{
+}
+
+int mail::smapFOLDER::process(imap &imapAccount, std::string &buffer)
+{
+ return 0;
+}
+
+const char mail::smapFOLDER::name[]="smapFOLDER";
+
+const char *mail::smapFOLDER::getName()
+{
+ return name;
+}
+
+void mail::smapFOLDER::timedOut(const char *errmsg)
+{
+}