/* ** Copyright 2003-2004, Double Precision Inc. ** ** See COPYING for distribution information. */ #include "libmail_config.h" #include "imap.H" #include "misc.H" #include "imaphandler.H" #include "imapidle.H" #include "imapfetchhandler.H" #include "imapfolder.H" #include "imapparsefmt.H" #include "smapopen.H" #include "smapidle.H" #include "smapnoopexpunge.H" #include "smapstore.H" #include "smapcopy.H" #include "smapsearch.H" #include "generic.H" #include "copymessage.H" #include "search.H" #include "rfcaddr.H" #include "envelope.H" #include "structure.H" #include "rfc822/rfc822.h" #include #include #include #include #include #include using namespace std; mail::imapFOLDERinfo::imapFOLDERinfo(string pathArg, mail::callback::folder &folderCallbackArg) : folderCallback(folderCallbackArg), exists(0), closeInProgress(false), path(pathArg) { } mail::imapFOLDERinfo::~imapFOLDERinfo() { } mail::imapFOLDERinfo::indexInfo::indexInfo() { } mail::imapFOLDERinfo::indexInfo::~indexInfo() { } void mail::imapFOLDERinfo::setUid(size_t count, string uid) { if (count < index.size()) index[count].uid=uid; } void mail::imapFOLDERinfo::opened() { } ///////////////////////////////////////////////////////////////////////// // // Helper class for processing the SELECT command. It's installed when // a SELECT command is issued. It handles the * FLAGS, * OK, and * NO // untagged messages, during folder open, as well as the * n EXISTS message // ( TODO - ignore any other garbage ) // LIBMAIL_START class imapSELECT : public imapCommandHandler { mail::callback &openCallback; mail::callback::folder &folderCallback; string path; public: string uidv; size_t exists; imapSELECT(string pathArg, mail::callback &openCallbackArg, mail::callback::folder &folderCallbackArg); ~imapSELECT(); void installed(imap &imapAccount); static const char name[]; private: const char *getName(); void timedOut(const char *errmsg); bool untaggedMessage(imap &imapAccount, string name); bool taggedMessage(imap &imapAccount, string name, string message, bool okfail, string errmsg); }; LIBMAIL_END const char mail::imapSELECT::name[]="SELECT"; mail::imapSELECT::imapSELECT(string pathArg, mail::callback &openCallbackArg, mail::callback::folder &folderCallbackArg) : openCallback(openCallbackArg), folderCallback(folderCallbackArg), path(pathArg), uidv(""), exists(0) { } mail::imapSELECT::~imapSELECT() { } void mail::imapSELECT::installed(mail::imap &imapAccount) { imapAccount.imapcmd("SELECT", "SELECT " + imapAccount.quoteSimple(path) + "\r\n"); imapAccount.folderFlags.clear(); imapAccount.permanentFlags.clear(); } const char *mail::imapSELECT::getName() { return name; } void mail::imapSELECT::timedOut(const char *errmsg) { callbackTimedOut(openCallback, errmsg); } // // Helper class for processing the untagged FLAGS response // LIBMAIL_START class imapSELECT_FLAGS : public imapHandlerStructured { void (imapSELECT_FLAGS::*next_func)(imap &, Token t); public: imapSELECT_FLAGS(); ~imapSELECT_FLAGS(); void installed(imap &imapAccount); private: const char *getName(); void timedOut(const char *errmsg); void process(imap &imapAccount, Token t); void start_flags(imap &imapAccount, Token t); void process_flags(imap &imapAccount, Token t); }; LIBMAIL_END mail::imapSELECT_FLAGS::imapSELECT_FLAGS() : next_func(&mail::imapSELECT_FLAGS::start_flags) { } mail::imapSELECT_FLAGS::~imapSELECT_FLAGS() { } void mail::imapSELECT_FLAGS::installed(mail::imap &imapAccount) { } const char *mail::imapSELECT_FLAGS::getName() { return ("* FLAGS"); } void mail::imapSELECT_FLAGS::timedOut(const char *errmsg) { } void mail::imapSELECT_FLAGS::process(mail::imap &imapAccount, Token t) { (this->*next_func)(imapAccount, t); } // // Helper class for processing an untagged * #nnnn response during a SELECT // LIBMAIL_START class imapSELECT_COUNT : public imapHandlerStructured { imapSELECT &select; void (imapSELECT_COUNT::*next_func)(imap &, Token t); size_t count; public: imapSELECT_COUNT(imapSELECT &mySelect, size_t myCount); ~imapSELECT_COUNT(); void installed(imap &imapAccount); private: const char *getName(); void timedOut(const char *); void process(imap &imapAccount, Token t); void get_count(imap &imapAccount, Token t); }; LIBMAIL_END mail::imapSELECT_COUNT::imapSELECT_COUNT(mail::imapSELECT &mySelect, size_t myCount) : select(mySelect), next_func(&mail::imapSELECT_COUNT::get_count), count(myCount) { } mail::imapSELECT_COUNT::~imapSELECT_COUNT() { } void mail::imapSELECT_COUNT::installed(mail::imap &imapAccount) { } const char *mail::imapSELECT_COUNT::getName() { return ("* #"); } void mail::imapSELECT_COUNT::timedOut(const char *errmsg) { } void mail::imapSELECT_COUNT::process(mail::imap &imapAccount, Token t) { (this->*next_func)(imapAccount, t); } // // Helper class for processing an untagged * OK response during a SELECT // LIBMAIL_START class imapSELECT_OK : public imapHandler { imapSELECT &select; string type; public: imapSELECT_OK(imapSELECT &mySelect, string typeArg); ~imapSELECT_OK(); void installed(imap &imapAccount); private: const char *getName(); void timedOut(const char *errmsg); int process(imap &imapAccount, string &buffer); void process(string &buffer); }; LIBMAIL_END mail::imapSELECT_OK::imapSELECT_OK(mail::imapSELECT &mySelect, string typeArg) : select(mySelect), type(typeArg) { upper(type); } mail::imapSELECT_OK::~imapSELECT_OK() { } void mail::imapSELECT_OK::installed(mail::imap &imapAccount) { } const char *mail::imapSELECT_OK::getName() { return("* OK"); } void mail::imapSELECT_OK::timedOut(const char *errmsg) { } ////////////////////////////////////////////////////////////////////////// // // After a SELECT, and any time * n EXISTS received, a SELECT FLAGS UID // is automatically issued (either for the entire folder, or just the new // messages). When this command completes, the application callback methods // will be invoked accordingly. // LIBMAIL_START class imapSYNCHRONIZE : public imapCommandHandler { mail::callback &callback; size_t fetchedCounter; size_t newExists; public: static size_t counter; // Keeps track of the # of objects in existence. imapSYNCHRONIZE(mail::callback &callbackArg); ~imapSYNCHRONIZE(); void installed(imap &imapAccount); void fetchedUID(); private: const char *getName(); void timedOut(const char *errmsg); bool untaggedMessage(imap &imapAccount, string name); bool taggedMessage(imap &imapAccount, string name, string message, bool okfail, string errmsg); }; LIBMAIL_END const char *mail::imapSYNCHRONIZE::getName() { return("SYNC"); } void mail::imapSYNCHRONIZE::timedOut(const char *errmsg) { callbackTimedOut(callback, errmsg); } /////////////////////////////////////////////////////////////////////// // // Stub call from mail::imapFolder::open() // void mail::imap::openFolder(string path, mail::snapshot *restoreSnapshot, mail::callback &openCallback, mail::callback::folder &folderCallback) { if (!ready(openCallback)) return; // If an existing folder is opened, tell the folder not to poll for // new mail any more. if (currentFolder) currentFolder->closeInProgress=true; installForegroundTask(smap ? (imapHandler *) new mail::smapOPEN(path, restoreSnapshot, openCallback, folderCallback) : new mail::imapSELECT(path, openCallback, folderCallback)); } // // A manually-requested NOOP. // LIBMAIL_START class imapCHECKMAIL : public imapCommandHandler { mail::callback &callback; public: imapCHECKMAIL(mail::callback &myCallback); ~imapCHECKMAIL(); void installed(imap &imapAccount); class dummyCallback : public mail::callback { public: dummyCallback(); ~dummyCallback(); void success(string); void fail(string); void reportProgress(size_t bytesCompleted, size_t bytesEstimatedTotal, size_t messagesCompleted, size_t messagesEstimatedTotal); }; private: const char *getName(); void timedOut(const char *errmsg); bool untaggedMessage(imap &imapAccount, string name); bool taggedMessage(imap &imapAccount, string name, string message, bool okfail, string errmsg); }; LIBMAIL_END mail::imapCHECKMAIL::imapCHECKMAIL(mail::callback &myCallback) : callback(myCallback) { } mail::imapCHECKMAIL::~imapCHECKMAIL() { } void mail::imapCHECKMAIL::installed(mail::imap &imapAccount) { imapAccount.imapcmd("NOOP2", "NOOP\r\n"); } const char *mail::imapCHECKMAIL::getName() { return "NOOP2"; } void mail::imapCHECKMAIL::timedOut(const char *errmsg) { callbackTimedOut(callback, errmsg); } bool mail::imapCHECKMAIL::untaggedMessage(mail::imap &imapAccount, string name) { return false; } bool mail::imapCHECKMAIL::taggedMessage(mail::imap &imapAccount, string name, string message, bool okfail, string errmsg) { // The check for completion of a manual new mail check must be // done slightly differently. That's because new mail // processing is handled by the regular new mail processing // objects, which don't really know anything about us. // If new mail processing got kicked off, there should be a // mail::imapSYNCHRONIZE object floating around somewhere. // If there is, what we do is we throw another checkNewMail // object in the queue, which should float to the beginning of // the queue after the current mail::imapSYNCHRONIZE object // goes away. if (name == "NOOP2") { if (mail::imapSYNCHRONIZE::counter == 0) { callback.success("DONE"); imapAccount.uninstallHandler(this); return true; } imapAccount.installForegroundTask(new mail::imapCHECKMAIL(callback)); imapAccount.uninstallHandler(this); return true; } return false; } mail::imapCHECKMAIL::dummyCallback::dummyCallback() { } mail::imapCHECKMAIL::dummyCallback::~dummyCallback() { } void mail::imapCHECKMAIL::dummyCallback::success(string dummy) { delete this; } void mail::imapCHECKMAIL::dummyCallback::fail(string dummy) { delete this; } void mail::imapCHECKMAIL ::dummyCallback::reportProgress(size_t bytesCompleted, size_t bytesEstimatedTotal, size_t messagesCompleted, size_t messagesEstimatedTot) { } void mail::imap::checkNewMail(mail::callback &callback) { if (!ready(callback)) return; if (currentFolder) currentFolder->resetMailCheckTimer(); installForegroundTask(smap ? (imapHandler *)new smapNoopExpunge("NOOP", callback, *this) : new mail::imapCHECKMAIL(callback)); } void mail::imapFOLDER::resetMailCheckTimer() { // Reset the regular new mail checking interval setTimeout(mailCheckInterval); } ////////////////////////////////////////////////////////////////////////////// // SELECT processing. bool mail::imapSELECT::untaggedMessage(mail::imap &imapAccount, string name) { if (name == "FLAGS") { imapAccount.installBackgroundTask(new mail::imapSELECT_FLAGS() ); return true; } if (name == "OK" || name == "NO") { imapAccount.installBackgroundTask(new mail::imapSELECT_OK(*this, name)); return true; } if (isdigit((int)(unsigned char)*name.begin())) { size_t c=0; istringstream(name.c_str()) >> c; imapAccount.installBackgroundTask(new mail::imapSELECT_COUNT(*this, c)); return true; } return false; } void mail::imapSELECT_FLAGS::start_flags(mail::imap &imapAccount, Token t) { imapAccount.folderFlags.clear(); if (t == NIL) { done(); return; } if (t != '(') error(imapAccount); next_func= &mail::imapSELECT_FLAGS::process_flags; } void mail::imapSELECT_FLAGS::process_flags(mail::imap &imapAccount, Token t) { if (t == ATOM) { string name=t.text; upper(name); imapAccount.folderFlags.insert(name); return; } if (t != ')') error(imapAccount); done(); } void mail::imapSELECT_COUNT::get_count(mail::imap &imapAccount, Token t) { if (t != ATOM) error(imapAccount); else { if (strcasecmp(t.text.c_str(), "EXISTS") == 0) { select.exists=count; // Check against hostile servers if (select.exists > UINT_MAX / sizeof(mail::messageInfo)) select.exists=UINT_MAX / sizeof(mail::messageInfo); } } done(); } int mail::imapSELECT_OK::process(mail::imap &imapAccount, string &buffer) { size_t p=buffer.find('\n'); if (p == std::string::npos) { if (p + 16000 < buffer.size()) return buffer.size() - 16000; // SANITY CHECK - DON'T LET HOSTILE SERVER DOS us return 0; } string buffer_cpy=buffer; buffer_cpy.erase(p); process(buffer_cpy); imapAccount.uninstallHandler(this); return p+1; } void mail::imapSELECT_OK::process(string &buffer) { if (type != "OK") return; string::iterator b=buffer.begin(), e=buffer.end(); while (b != e && unicode_isspace((unsigned char)*b)) b++; if (b == e || *b != '[') return; b++; string w=mail::imap::get_word(&b, &e); upper(w); if (w == "UIDVALIDITY") // Memorize * OK [UIDVALIDITY] { w=mail::imap::get_word(&b, &e); select.uidv=w; } else if (w == "PERMANENTFLAGS") { myimap->setPermanentFlags(b, e); } } // // After an initial SELECT succeeds, even if the subsequent resynchronization // commands fail, we still want to indicate success. // LIBMAIL_START class imapSELECTCallback : public mail::callback { mail::callback &origCallback; void reportProgress(size_t bytesCompleted, size_t bytesEstimatedTotal, size_t messagesCompleted, size_t messagesEstimatedTotal); public: imapSELECTCallback(mail::callback &cb); ~imapSELECTCallback(); void success(string msg); void fail(string msg); }; LIBMAIL_END bool mail::imapSELECT::taggedMessage(mail::imap &imapAccount, string name, string message, bool okfail, string errmsg) { if (name != "SELECT") return false; if (okfail && uidv.length() == 0) { okfail=false; errmsg="Server did not return the folder UID-validity token."; } if (okfail) { mail::imapFOLDER *f=new mail::imapFOLDER(path, uidv, exists, folderCallback, imapAccount); imapAccount.installBackgroundTask(f); imapAccount.currentFolder=f; f->exists=0; if (exists > 0) { mail::imapSELECTCallback *cb= new mail::imapSELECTCallback(openCallback); try { imapAccount.installForegroundTask (new mail::imapSYNCHRONIZE(*cb)); } catch (...) { delete cb; } } else openCallback.success("Empty folder."); } else { imapAccount.currentFolder=NULL; openCallback.fail(errmsg); } imapAccount.uninstallHandler(this); return true; } mail::imapSELECTCallback::imapSELECTCallback(mail::callback &cb) : origCallback(cb) { } mail::imapSELECTCallback::~imapSELECTCallback() { } void mail::imapSELECTCallback::success(string msg) { origCallback.success(msg); delete this; } void mail::imapSELECTCallback::fail(string msg) { origCallback.success(msg); delete this; } void mail::imapSELECTCallback::reportProgress(size_t bytesCompleted, size_t bytesEstimatedTotal, size_t messagesCompleted, size_t messagesEstimatedTotal) { origCallback.reportProgress(bytesCompleted, bytesEstimatedTotal, messagesCompleted, messagesEstimatedTotal); } //////////////////////////////////////////////////////////////////////// // // Helper class to resynchronize a folder size_t mail::imapSYNCHRONIZE::counter=0; mail::imapSYNCHRONIZE::imapSYNCHRONIZE(mail::callback &callbackArg) : callback(callbackArg), fetchedCounter(0) { ++counter; } void mail::imapSYNCHRONIZE::installed(mail::imap &imapAccount) { ostringstream o; newExists=imapAccount.currentFolder ? imapAccount.currentFolder->index.size():0; if (imapAccount.currentFolder == NULL || imapAccount.currentFolder->exists >= newExists) { imapAccount.currentFolder->exists=newExists; // Insurance o << "NOOP\r\n"; } else { o << "FETCH " << (imapAccount.currentFolder->exists+1) << ":" << newExists << " (UID FLAGS)\r\n"; } imapAccount.imapcmd("SYNC", o.str()); imapAccount.currentSYNCHRONIZE=this; } mail::imapSYNCHRONIZE::~imapSYNCHRONIZE() { if (myimap) myimap->currentSYNCHRONIZE=NULL; --counter; } void mail::imapSYNCHRONIZE::fetchedUID() { size_t c=myimap && myimap->currentFolder ? myimap->currentFolder->exists:0; if (c >= newExists) c=newExists; if (fetchedCounter < newExists - c) { ++fetchedCounter; callback.reportProgress(0, 0, fetchedCounter, newExists - c); } } bool mail::imapSYNCHRONIZE::untaggedMessage(mail::imap &imapAccount, string name) { return false; } bool mail::imapSYNCHRONIZE::taggedMessage(mail::imap &imapAccount, string name, string message, bool okfail, string errmsg) { if (name != "SYNC") return false; if (!okfail) { callback.fail(errmsg); imapAccount.uninstallHandler(this); return true; } mail::imapFOLDERinfo *f=imapAccount.currentFolder; size_t n; for (n=f->exists; nindex.size(); n++) if (f->index[n].uid.length() == 0) { callback.fail("Server failed to respond with message identifier."); imapAccount.uninstallHandler(this); return true; } if (f->exists < newExists) f->exists=newExists; callback.success("Folder index updated."); imapAccount.uninstallHandler(this); return true; } size_t mail::imap::getFolderIndexSize() { if (currentFolder == NULL) return 0; return currentFolder->exists; } mail::messageInfo mail::imap::getFolderIndexInfo(size_t n) { if (currentFolder != NULL && n < currentFolder->exists) return currentFolder->index[n]; return mail::messageInfo(); } void mail::imap::getFolderKeywordInfo(size_t n, std::set &kwset) { if (currentFolder != NULL && n < currentFolder->exists) { currentFolder->index[n].keywords.getFlags(kwset); } else kwset.clear(); } ////////////////////////////////////////////////////////////////////////// // // Server replies when a folder is currently open. // const char mail::imapFOLDER::name[]="FolderHandler"; // // Helper class for processing an untagged * #nnnn response during a SELECT // LIBMAIL_START class imapFOLDER_COUNT : public imapHandlerStructured { void (imapFOLDER_COUNT::*next_func)(imap &, Token t); size_t count; bool body_part_reference; // Fetch References: header string references_buf; vector flags; bool accept_largestring; bool wantLargeString(); public: imapFOLDER_COUNT(size_t myCount); ~imapFOLDER_COUNT(); void installed(imap &imapAccount) {} private: const char *getName(); void timedOut(const char *errmsg); void process(imap &imapAccount, Token t); void get_count(imap &imapAccount, Token t); void get_fetch(imap &imapAccount, Token t); void get_fetch_item(imap &imapAccount, Token t); void get_fetch_uid(imap &imapAccount, Token t); void get_fetch_flags(imap &imapAccount, Token t); void get_fetch_flag(imap &imapAccount, Token t); void saveflags(imap &imapAccount); void get_internaldate(imap &imapAccount, Token t); void get_rfc822size(imap &imapAccount, Token t); void get_envelope(imap &imapAccount, Token t); void get_bodystructure(imap &imapAccount, Token t); void get_body(imap &imapAccount, Token t); bool fillbodystructure(imap &imapAccount, imapparsefmt &parser, mimestruct &bodystructure, string mime_id); imapparsefmt parser; }; LIBMAIL_END mail::imapFOLDER::imapFOLDER(string pathArg, string uidvArg, size_t existsArg, mail::callback::folder &folderCallbackArg, mail::imap &myserver) : mail::imapCommandHandler(myserver.noopSetting), imapFOLDERinfo(pathArg, folderCallbackArg), existsNotify(NULL), uidv(uidvArg) { mailCheckInterval=myserver.noopSetting; setBackgroundTask(true); index.resize(existsArg); } mail::imapFOLDER::~imapFOLDER() { if (existsNotify) existsNotify->me=NULL; } mail::imapFOLDER::existsCallback::existsCallback() { } mail::imapFOLDER::existsCallback::~existsCallback() { } void mail::imapFOLDER::existsCallback::success(string message) { if (me) { mail::imapFOLDER *cb=me; me=NULL; cb->existsNotify=NULL; cb->folderCallback.newMessages(); } delete this; } void mail::imapFOLDER::existsCallback ::reportProgress(size_t bytesCompleted, size_t bytesEstimatedTotal, size_t messagesCompleted, size_t messagesEstimatedTotal) { } void mail::imapFOLDER::existsCallback::fail(string message) { if (me) { mail::imapFOLDER *cb=me; me=NULL; cb->existsNotify=NULL; cb->folderCallback.newMessages(); } delete this; } const char *mail::imapFOLDER::getName() { return name; } void mail::imapFOLDER::installed(mail::imap &imapAccount) { } void mail::imapFOLDER::timedOut(const char *errmsg) { } int mail::imapFOLDER::getTimeout(mail::imap &imapAccount) { int t=mail::imapCommandHandler::getTimeout(imapAccount); if (t == 0) { setTimeout(t=imapAccount.noopSetting); imapHandler *h; if (!closeInProgress // Not closing the folder && ((h=imapAccount.hasForegroundTask()) == NULL || strcmp(h->getName(), "IDLE") == 0)) // Something else is already in progress { imapCHECKMAIL::dummyCallback *dummy= new imapCHECKMAIL::dummyCallback(); if (!dummy) LIBMAIL_THROW((strerror(errno))); try { imapAccount .installForegroundTask(new imapCHECKMAIL (*dummy)); } catch (...) { delete dummy; throw; } } } return t; // This handler never causes a timeout } bool mail::imapFOLDER::untaggedMessage(mail::imap &imapAccount, string name) { if (isdigit((int)(unsigned char)*name.begin())) { if (imapAccount.handlers.count(mail::imapSELECT::name) > 0) return false; // SELECT in progress size_t c=0; istringstream(name.c_str()) >> c; imapAccount.installBackgroundTask(new mail::imapFOLDER_COUNT(c)); return true; } if (name == "FLAGS") { imapAccount .installBackgroundTask(new mail::imapSELECT_FLAGS()); return true; } return false; } bool mail::imapFOLDER::taggedMessage(mail::imap &imapAccount, string name, string message, bool okfail, string errmsg) { return false; } mail::imapFOLDER_COUNT::imapFOLDER_COUNT(size_t myCount) : next_func(&mail::imapFOLDER_COUNT::get_count), count(myCount), body_part_reference(false), accept_largestring(false) { } mail::imapFOLDER_COUNT::~imapFOLDER_COUNT() { } bool mail::imapFOLDER_COUNT::wantLargeString() { return accept_largestring; } const char *mail::imapFOLDER_COUNT::getName() { return ("* #"); } void mail::imapFOLDER_COUNT::timedOut(const char *errmsg) { } void mail::imapFOLDER_COUNT::process(mail::imap &imapAccount, Token t) { (this->*next_func)(imapAccount, t); } // Seen * nnnnn void mail::imapFOLDER_COUNT::get_count(mail::imap &imapAccount, Token t) { if (t != ATOM) { error(imapAccount); return; } string name=t.text; upper(name); if (name == "FETCH") { next_func= &mail::imapFOLDER_COUNT::get_fetch; return; } if (name == "EXPUNGE") { done(); if (imapAccount.currentFolder != NULL && count > 0 && count <= imapAccount.currentFolder->index.size()) { size_t c=count-1; vector &indexRef= imapAccount.currentFolder->index; indexRef.erase(indexRef.begin() + c, indexRef.begin() + c + 1); if (c < imapAccount.currentFolder->exists) { --imapAccount.currentFolder->exists; vector< pair > a; a.push_back(make_pair(c, c)); imapAccount.currentFolder->folderCallback .messagesRemoved(a); } } return; } if (name == "EXISTS") { done(); if (imapAccount.currentFolder != NULL && !imapAccount.currentFolder->closeInProgress && count > imapAccount.currentFolder->index.size()) { imapAccount.currentFolder ->existsMore(imapAccount, count); } } done(); } void mail::imapFOLDER::existsMore(mail::imap &imapAccount, size_t count) { if (closeInProgress) return; if (exists > count) exists=count; index.resize(count, imapFOLDERinfo::indexInfo()); if (existsNotify) existsNotify->me=NULL; existsNotify=new mail::imapFOLDER::existsCallback; if (!existsNotify) LIBMAIL_THROW("Out of memory."); existsNotify->me=this; imapAccount .installForegroundTask(new mail::imapSYNCHRONIZE(*existsNotify) ); } void mail::imapFOLDER_COUNT::get_fetch(mail::imap &imapAccount, Token t) { if (t != '(') error(imapAccount); next_func= &mail::imapFOLDER_COUNT::get_fetch_item; } void mail::imapFOLDER_COUNT::get_fetch_item(mail::imap &imapAccount, Token t) { accept_largestring=false; if (t == ')') { done(); return; } if (t != ATOM) { error(imapAccount); return; } string name=t.text; upper(name); if (name == "UID") { next_func= &mail::imapFOLDER_COUNT::get_fetch_uid; return; } if (name == "FLAGS") { next_func= &mail::imapFOLDER_COUNT::get_fetch_flags; return; } if (name == "INTERNALDATE") { next_func= &mail::imapFOLDER_COUNT::get_internaldate; return; } if (name == "RFC822.SIZE") { next_func= &mail::imapFOLDER_COUNT::get_rfc822size; return; } if (name == "ENVELOPE") { parser.begin(); next_func= &mail::imapFOLDER_COUNT::get_envelope; return; } if (name == "BODYSTRUCTURE") { parser.begin(); next_func= &mail::imapFOLDER_COUNT::get_bodystructure; return; } if (strncasecmp(name.c_str(), "BODY[", 5) == 0 || strncasecmp(name.c_str(), "BODY.PEEK[", 10) == 0) { accept_largestring=true; next_func= &mail::imapFOLDER_COUNT::get_body; size_t i=name.find(']'); if (i != name.length()-1) { error(imapAccount); return; } name.erase(i); name.erase(0, name.find('[')+1); body_part_reference=strncasecmp(name.c_str(), "HEADER.FIELDS", 13) == 0; references_buf=""; // Getting the References: header. return; } error(imapAccount); } void mail::imapFOLDER_COUNT::get_fetch_uid(mail::imap &imapAccount, Token t) { if (t != ATOM) { error(imapAccount); return; } if (imapAccount.currentFolder && count > 0) imapAccount.currentFolder->setUid(count-1, t.text); // Report progress if (imapAccount.currentSYNCHRONIZE) imapAccount.currentSYNCHRONIZE->fetchedUID(); next_func= &mail::imapFOLDER_COUNT::get_fetch_item; } void mail::imapFOLDER::setUid(size_t count, string uid) { imapFOLDERinfo::setUid(count, uidv + "/" + uid); } void mail::imapFOLDER_COUNT::get_fetch_flags(mail::imap &imapAccount, Token t) { flags.clear(); if (t == NIL) { saveflags(imapAccount); return; } if (t != '(') { error(imapAccount); return; } next_func= &mail::imapFOLDER_COUNT::get_fetch_flag; } void mail::imapFOLDER_COUNT::get_fetch_flag(mail::imap &imapAccount, Token t) { if (t == ')') { saveflags(imapAccount); return; } if (t != ATOM) { error(imapAccount); return; } flags.push_back(t.text); } void mail::imapFOLDER_COUNT::saveflags(mail::imap &imapAccount) { next_func= &mail::imapFOLDER_COUNT::get_fetch_item; if (imapAccount.currentFolder && count > 0 && count <= imapAccount.currentFolder->index.size()) { imapFOLDERinfo::indexInfo &i= imapAccount.currentFolder->index[count-1]; if (imapAccount.folderFlags.count("\\DRAFT") > 0) i.draft=0; if (imapAccount.folderFlags.count("\\ANSWERED") > 0) i.replied=0; if (imapAccount.folderFlags.count("\\FLAGGED") > 0) i.marked=0; if (imapAccount.folderFlags.count("\\DELETED") > 0) i.deleted=0; if (imapAccount.folderFlags.count("\\SEEN") > 0) i.unread=1; if (imapAccount.folderFlags.count("\\RECENT") > 0) i.recent=0; size_t n; mail::keywords::Message newMessage; for (n=0; nexists) imapAccount.currentFolder-> folderCallback.messageChanged(count-1); } } static void parse_addr_list(mail::imapparsefmt *list, vector &addresses) { vector::iterator b=list->children.begin(), e=list->children.end(); while (b != e) { mail::imapparsefmt *address= *b; if (address->children.size() >= 4) { mail::imapparsefmt *name=address->children[0]; mail::imapparsefmt *mailbox=address->children[2]; mail::imapparsefmt *host=address->children[3]; if (host->nil) { if (mailbox->nil) addresses. push_back(mail::address(";", "")); else addresses. push_back(mail::address(mailbox ->value + ":", "")); } else { addresses. push_back(mail::address(name->value, mailbox->value + "@" + host->value)); } } b++; } } static bool fillenvelope(mail::imapparsefmt &imapenvelope, mail::envelope &envelope) { if (imapenvelope.children.size() < 10) return false; vector::iterator b=imapenvelope.children.begin(); envelope.date=rfc822_parsedt((*b)->value.c_str()); b++; envelope.subject= (*b)->value; b++; parse_addr_list(*b, envelope.from); b++; parse_addr_list(*b, envelope.sender); b++; parse_addr_list(*b, envelope.replyto); b++; parse_addr_list(*b, envelope.to); b++; parse_addr_list(*b, envelope.cc); b++; parse_addr_list(*b, envelope.bcc); b++; envelope.inreplyto= (*b)->value; b++; envelope.messageid= (*b)->value; return true; } void mail::imapFOLDER_COUNT::get_internaldate(mail::imap &imapAccount, Token t) { if (t != ATOM && t != STRING) { error(imapAccount); return; } if (imapAccount.currentFetch) { // Make IMAP date presentable for rfc822_parsedt() by // replacing dd-mmm-yyyy with dd mmm yyyy string d=t.text; if (d[0] == ' ') d[0]='0'; size_t spacepos=d.find(' '); size_t n; while ((n=d.find('-')) < spacepos) d[n]=' '; time_t tm=rfc822_parsedt(d.c_str()); if (tm) imapAccount.currentFetch ->messageArrivalDateCallback(count-1, tm); } next_func= &mail::imapFOLDER_COUNT::get_fetch_item; } void mail::imapFOLDER_COUNT::get_rfc822size(mail::imap &imapAccount, Token t) { if (t != ATOM) { error(imapAccount); return; } if (imapAccount.currentFetch) { unsigned long msgsize=0; istringstream(t.text.c_str()) >> msgsize; imapAccount.currentFetch ->messageSizeCallback(count-1, msgsize); } next_func= &mail::imapFOLDER_COUNT::get_fetch_item; } void mail::imapFOLDER_COUNT::get_envelope(mail::imap &imapAccount, Token t) { bool err_flag; if (!parser.process(imapAccount, t, err_flag)) return; // Not yet next_func= &mail::imapFOLDER_COUNT::get_fetch_item; mail::envelope envelope; if (err_flag || !fillenvelope(parser, envelope)) { error(imapAccount); return; } if (imapAccount.currentFetch) imapAccount.currentFetch ->messageEnvelopeCallback(count-1, envelope); } static void fill_type_parameters(mail::imapparsefmt *ptr, mail::mimestruct &bodystructure) { vector::iterator bb=ptr->children.begin(), ee=ptr->children.end(); while (bb != ee) { string name= (*bb)->value; bb++; if (bb != ee) { bodystructure.type_parameters. set_simple(name, (*bb)->value); bb++; } } } static void fill_disposition(mail::imapparsefmt *ptr, mail::mimestruct &bodystructure) { if (ptr->children.size() > 0) bodystructure.content_disposition= ptr->children[0]->value; if (ptr->children.size() < 2) return; vector::iterator bb=ptr->children[1]->children.begin(), ee=ptr->children[1]->children.end(); while (bb != ee) { string name= (*bb)->value; bb++; if (bb != ee) { bodystructure.content_disposition_parameters .set_simple(name, (*bb)->value); bb++; } } } bool mail::imapFOLDER_COUNT::fillbodystructure(mail::imap &imapAccount, mail::imapparsefmt &parser, mail::mimestruct &bodystructure, string mime_id) { vector::iterator b=parser.children.begin(), e=parser.children.end(); if (b == e) { error(imapAccount); return false; } if ( (*b)->children.size() == 0) // Non-multipart { bodystructure.mime_id=mime_id; if (mime_id.length() == 0) mime_id="1"; bodystructure.type= (*b)->value; b++; if (b != e) { bodystructure.subtype= (*b)->value; b++; } mail::upper(bodystructure.type); mail::upper(bodystructure.subtype); if (b != e) { fill_type_parameters( *b, bodystructure ); b++; } if (b != e) { bodystructure.content_id= (*b)->value; b++; } if (b != e) { bodystructure.content_description= (*b)->value; b++; } if (b != e) { bodystructure.content_transfer_encoding= (*b)->value; b++; } if (b != e) { istringstream((*b)->value.c_str()) >> bodystructure.content_size; b++; if (bodystructure.messagerfc822()) { if (b != e) { if (!fillenvelope(**b, bodystructure .getEnvelope()) ) { error(imapAccount); return false; } b++; } if (b != e) { if ( (*b)->children.size() == 0) mime_id=mime_id + ".1"; mail::mimestruct &child_struct= *bodystructure.addChild(); if (!fillbodystructure(imapAccount, **b, child_struct, mime_id)) return false; if (child_struct.getNumChildren() == 0) child_struct.mime_id= mime_id + ".1"; // Fixup } if (b != e) { istringstream((*b)->value.c_str()) >> bodystructure.content_lines; } } else if (bodystructure.type == "TEXT") { if (b != e) { istringstream((*b)->value.c_str()) >> bodystructure.content_lines; b++; } } } if (b != e) { bodystructure.content_md5= (*b)->value; b++; } } else // MULTIPART { size_t body_num=1; bodystructure.mime_id=mime_id; if (mime_id.length() > 0) mime_id=mime_id+"."; #if 0 else bodystructure.mime_id="1"; #endif while (b != e) { if ( (*b)->children.size() == 0) break; string buffer; { ostringstream o; o << body_num; buffer=o.str(); } body_num++; if (!fillbodystructure(imapAccount, **b, *bodystructure.addChild(), mime_id + buffer)) return false; b++; } bodystructure.type="MULTIPART"; bodystructure.subtype="MIXED"; if (b != e) { bodystructure.subtype= (*b)->value; b++; } mail::upper(bodystructure.subtype); if (b != e) { fill_type_parameters( *b, bodystructure ); b++; } } if (b != e) { fill_disposition( (*b), bodystructure ); b++; } if (b != e) { bodystructure.content_language= (*b)->value; b++; } return true; } void mail::imapFOLDER_COUNT::get_bodystructure(mail::imap &imapAccount, Token t) { bool err_flag; if (!parser.process(imapAccount, t, err_flag)) return; // Not yet next_func= &mail::imapFOLDER_COUNT::get_fetch_item; mail::mimestruct bodystructure; if (err_flag) { error(imapAccount); return; } if (!fillbodystructure(imapAccount, parser, bodystructure, "")) return; if (imapAccount.currentFetch) imapAccount.currentFetch ->messageStructureCallback(count-1, bodystructure); } void mail::imapFOLDER_COUNT::get_body(mail::imap &imapAccount, Token t) { if (t == STRING) next_func= &mail::imapFOLDER_COUNT::get_fetch_item; if (t == STRING || t == LARGESTRING) { string buf=t.text; size_t cr; while ((cr=buf.find('\r')) != std::string::npos) buf.erase(cr, 1); if (imapAccount.currentFetch) { imapAccount.currentFetch->messageTextEstimatedSize = t == LARGESTRING ? largestringsize:t.text.size(); imapAccount.currentFetch ->messageTextCompleted += t.text.size(); if (t == STRING) { imapAccount.currentFetch->messageTextCompleted = imapAccount.currentFetch-> messageTextEstimatedSize; ++imapAccount.currentFetch->messageCntDone; } if (body_part_reference) // Processing References: header { references_buf += buf; while (references_buf.size() > 10000) // Prevent the server from DOssing us { size_t n=references_buf.find('>'); if (n != std::string::npos) ++n; else n=references_buf.size()-10000; references_buf .erase(references_buf.begin(), references_buf.begin() +n); } if (t == STRING) { vector
address_list; size_t err_index; address::fromString(references_buf, address_list, err_index); vector msgid_list; vector
::iterator b=address_list.begin(), e=address_list.end(); while (b != e) { string s=b->getAddr(); if (s.size() > 0) msgid_list.push_back ("<" + s + ">"); ++b; } if (msgid_list.size() > 0) imapAccount.currentFetch-> messageReferencesCallback(count-1, msgid_list); } } else imapAccount.currentFetch ->messageTextCallback(count-1, buf); } } else error(imapAccount); } ///////////////////////////////////////////////////////////////////////////// // // The STORE command. // LIBMAIL_START class imapSTORE : public imapCommandHandler { mail::callback &callback; string storecmd; public: imapSTORE(mail::callback &callbackArg, string storecmdArg); ~imapSTORE(); void installed(imap &imapAccount); private: const char *getName(); void timedOut(const char *errmsg); bool untaggedMessage(imap &imapAccount, string name); bool taggedMessage(imap &imapAccount, string name, string message, bool okfail, string errmsg); }; LIBMAIL_END void mail::imapSTORE::timedOut(const char *errmsg) { callbackTimedOut(callback, errmsg); } mail::imapSTORE::imapSTORE(mail::callback &callbackArg, string storecmdArg) : callback(callbackArg), storecmd(storecmdArg) { } mail::imapSTORE::~imapSTORE() { } void mail::imapSTORE::installed(mail::imap &imapAccount) { imapAccount.imapcmd("STORE", storecmd + "\r\n"); } const char *mail::imapSTORE::getName() { return "STORE"; } bool mail::imapSTORE::untaggedMessage(mail::imap &imapAccount, string name) { return false; } bool mail::imapSTORE::taggedMessage(mail::imap &imapAccount, string name, string message, bool okfail, string errmsg) { if (name == "STORE") { if (okfail) callback.success(errmsg); else callback.fail(errmsg); imapAccount.uninstallHandler(this); return true; } return false; } bool mail::imap::keywordAllowed(string kwName) { upper(kwName); return permanentFlags.count(kwName) > 0 || permanentFlags.count("\\*") > 0; } void mail::imap::saveFolderIndexInfo(size_t messageNum, const mail::messageInfo &indexInfo, mail::callback &callback) { if (!ready(callback)) return; if (messageNum >= getFolderIndexSize()) { callback.success("Invalid message number - ignored."); return; } if (smap) { string flags; #define FLAG #define NOTFLAG ! #define DOFLAG(NOT, field, name) \ if (NOT indexInfo.field) flags += "," name LIBMAIL_SMAPFLAGS; #undef DOFLAG #undef NOTFLAG #undef FLAG if (flags.size() > 0) flags=flags.substr(1); installForegroundTask(new smapSTORE(messageNum, "FLAGS=" + flags, *this, callback)); return; } bool fakeUpdateFlag; string flags=messageFlagStr(indexInfo, ¤tFolder->index[messageNum], &fakeUpdateFlag); { set oflags; currentFolder->index[messageNum].keywords.getFlags(oflags); set::iterator b=oflags.begin(), e=oflags.end(); while (b != e) { if (keywordAllowed(*b)) { if (flags.size() > 0) flags += " "; flags += *b; } ++b; } } string messageNumBuffer; { ostringstream o; o << "STORE " << (messageNum+1) << " FLAGS ("; messageNumBuffer=o.str(); } installForegroundTask(new mail::imapSTORE(callback, messageNumBuffer + flags + ")")); if (fakeUpdateFlag && messageNum < currentFolder->exists) currentFolder->folderCallback.messageChanged(messageNum); } ///////////////////////////////////////////////////////////////////////// // // A STORE for a list may be broken up into multiple commands. // Count the number of tagged STORE acknowledgments received, and run the // app callback only after the last STORE ack comes in. LIBMAIL_START class imapUpdateFlagsCallback : public mail::callback { mail::callback &origCallback; void reportProgress(size_t bytesCompleted, size_t bytesEstimatedTotal, size_t messagesCompleted, size_t messagesEstimatedTotal); public: imapUpdateFlagsCallback(mail::callback &callback); ~imapUpdateFlagsCallback(); void success(string message); void fail(string message); bool failFlag; string message; size_t cnt; }; LIBMAIL_END mail::imapUpdateFlagsCallback::imapUpdateFlagsCallback(mail::callback &callback) : origCallback(callback), failFlag(false), message(""), cnt(0) { } mail::imapUpdateFlagsCallback::~imapUpdateFlagsCallback() { } void mail::imapUpdateFlagsCallback::success(string message) { // If any failures were received, the app fail() callback will be // invoked. if (!failFlag) this->message=message; if (--cnt == 0) { if (failFlag) origCallback.fail(message); else origCallback.success(message); delete this; } } void mail::imapUpdateFlagsCallback::fail(string message) { this->message=message; failFlag=true; success(message); } void mail::imapUpdateFlagsCallback ::reportProgress(size_t bytesCompleted, size_t bytesEstimatedTotal, size_t messagesCompleted, size_t messagesEstimatedTotal) { origCallback.reportProgress(bytesCompleted, bytesEstimatedTotal, messagesCompleted, messagesEstimatedTotal); } void mail::imap::updateFolderIndexFlags(const vector &messages, bool doFlip, bool enableDisable, const mail::messageInfo &flags, mail::callback &callback) { if (doFlip) { mail::imapUpdateFlagsCallback *myCallback= new mail::imapUpdateFlagsCallback(callback); if (!myCallback) { callback.fail("Out of memory."); return; } // // Very ugly, but this is the cleanest ugly solution. // For each flipped flag, segregate messages whose // flag should be turned on, and ones that should be // turned off, and issue a separate STORE for each set. // Lather, rinse, repeat, for all flags. // try { #define DOFLAG(NOT, field, name) { \ vector enabled, disabled;\ \ vector::const_iterator b=messages.begin(),\ e=messages.end();\ \ while (b != e)\ {\ size_t n= *b++;\ \ if (n >= getFolderIndexSize())\ continue;\ \ mail::messageInfo &info=currentFolder->index[n];\ \ if ( flags.field ) \ { \ if (info.field)\ disabled.push_back(n);\ else\ enabled.push_back(n);\ }\ }\ \ mail::messageInfo oneFlag;\ \ oneFlag.field=true;\ ++myCallback->cnt;\ updateFolderIndexFlags(enabled, true, oneFlag, *myCallback);\ ++myCallback->cnt;\ updateFolderIndexFlags(disabled, false, oneFlag, *myCallback);\ } ++myCallback->cnt; #define NOTFLAG #define FLAG LIBMAIL_MSGFLAGS; #undef NOTFLAG #undef FLAG #undef DOFLAG myCallback->success("Success"); } catch (...) { delete myCallback; callback.fail("An exception occured in updateFolderIndexFlags()"); } return; } updateFolderIndexFlags(messages, enableDisable, flags, callback); } LIBMAIL_START class imapMessageCallbackStub : public mail::callback::message { mail::callback &origCallback; void reportProgress(size_t bytesCompleted, size_t bytesEstimatedTotal, size_t messagesCompleted, size_t messagesEstimatedTotal); public: imapMessageCallbackStub(mail::callback &callbackArg); ~imapMessageCallbackStub(); void success(string message); void fail(string message); }; LIBMAIL_END mail::imapMessageCallbackStub ::imapMessageCallbackStub(mail::callback &callbackArg) : origCallback(callbackArg) { } mail::imapMessageCallbackStub::~imapMessageCallbackStub() { } void mail::imapMessageCallbackStub::success(string message) { origCallback.success(message); delete this; } void mail::imapMessageCallbackStub::fail(string message) { origCallback.fail(message); delete this; } void mail::imapMessageCallbackStub ::reportProgress(size_t bytesCompleted, size_t bytesEstimatedTotal, size_t messagesCompleted, size_t messagesEstimatedTotal) { origCallback.reportProgress(bytesCompleted, bytesEstimatedTotal, messagesCompleted, messagesEstimatedTotal); } void mail::imap::updateFolderIndexFlags(const vector &messages, bool enableDisable, const mail::messageInfo &flags, mail::callback &callback) { if (smap) { const char *plus="+FLAGS="; const char *minus="-FLAGS="; string lista; string listb; #define FLAG lista #define NOTFLAG listb #define DOFLAG(FLAG, field, name) \ if (flags.field) FLAG += "," name LIBMAIL_SMAPFLAGS; #undef DOFLAG #undef NOTFLAG #undef FLAG if (lista.size() > 0) lista=(enableDisable ? plus:minus) + lista.substr(1); if (listb.size() > 0) listb=(enableDisable ? minus:plus) + listb.substr(1); installForegroundTask(new smapSTORE(messages, lista + " " + listb, *this, callback)); return; } const char *plusminus=NULL; string name=""; #define DOFLAG(MARK, field, n) if ( flags.field ) { plusminus=MARK; name += " " n; } #define FLAG "+-" #define NOTFLAG "-+" LIBMAIL_MSGFLAGS; #undef FLAG #undef NOTFLAG #undef DOFLAG if (!plusminus || messages.size() == 0) { callback.success("Ok"); return; } name=name.substr(1); char buf[2]; buf[0]=plusminus[enableDisable ? 0:1]; buf[1]=0; mail::imapMessageCallbackStub *c= new mail::imapMessageCallbackStub(callback); if (!c) { callback.fail("Out of memory."); return; } try { messagecmd(messages, string(" ") + buf + "FLAGS (" + name + ")", "UID STORE", "STORE", *c); } catch (...) { delete c; LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); } } class mail::imap::updateImapKeywordHelper : public mail::callback { mail::callback *origCallback; mail::ptr myimap; public: set newflags; set uids; updateImapKeywordHelper(mail::callback *origCallbackArg, mail::imap *myimapArg); ~updateImapKeywordHelper(); void success(std::string message); void fail(std::string message); void reportProgress(size_t bytesCompleted, size_t bytesEstimatedTotal, size_t messagesCompleted, size_t messagesEstimatedTotal); }; void mail::imap::updateKeywords(const vector &messages, const set &keywords, bool setOrChange, // false: set, true: see changeTo bool changeTo, callback &cb) { set::const_iterator b=keywords.begin(), e=keywords.end(); while (b != e) { string::const_iterator sb=b->begin(), se=b->end(); while (sb != se) { if (strchr(smap ? ",\t\r\n,":"() \t\r\n[]{}\\\"", *sb)) { cb.fail("Invalid flag"); return; } ++sb; } ++b; } b=keywords.begin(); e=keywords.end(); if (smap) { string setCmd= setOrChange ? changeTo ? "+KEYWORDS=":"-KEYWORDS=":"KEYWORDS="; const char *comma=""; while (b != e) { setCmd += comma; setCmd += *b; ++b; comma=","; } installForegroundTask(new smapSTORE(messages, quoteSMAP(setCmd), *this, cb)); return; } if (!setOrChange) { updateImapKeywordHelper *h= new updateImapKeywordHelper(&cb, this); if (!h) { cb.fail(strerror(errno)); return; } try { h->newflags=keywords; map allFlags; // Keyed by upper(name), value=name // (be nice, and preserve casing when turning off // flags). vector::const_iterator mb=messages.begin(), me=messages.end(); while (mb != me) { size_t n= *mb; ++mb; if (n >= currentFolder->index.size()) continue; h->uids.insert(currentFolder->index[n].uid); set tempSet; currentFolder->index[n].keywords .getFlags(tempSet); set::iterator tsb=tempSet.begin(), tse=tempSet.end(); while (tsb != tse) { string flagName= *tsb; // IMAP kws are case insensitive mail::upper(flagName); allFlags.insert(make_pair(flagName, *tsb)); ++tsb; } } b=keywords.begin(); e=keywords.end(); while (b != e) { string flagName= *b; ++b; mail::upper(flagName); allFlags.erase(flagName); // No need to remove flags that are going to be // set anyway. } if (allFlags.empty()) h->success("Ok - no keywords changed."); // Nothing to turn off, so punt it. else { set allFlagsV; map::iterator b= allFlags.begin(), e=allFlags.end(); while (b != e) { allFlagsV.insert(b->second); ++b; } allFlags.clear(); updateImapKeywords(messages, allFlagsV, false, *h); } } catch (...) { delete h; } return; } updateImapKeywords(messages, keywords, changeTo, cb); } mail::imap::updateImapKeywordHelper ::updateImapKeywordHelper(mail::callback *origCallbackArg, mail::imap *myimapArg) : origCallback(origCallbackArg), myimap(myimapArg) { } mail::imap::updateImapKeywordHelper::~updateImapKeywordHelper() { if (origCallback) origCallback->fail("Aborted."); } void mail::imap::updateImapKeywordHelper::success(std::string message) { vector msgSet; mail::imapFOLDERinfo *f=myimap.isDestroyed() ? NULL: myimap->currentFolder; size_t n=f ? f->index.size():0; size_t i; for (i=0; iindex[i].uid) > 0) msgSet.push_back(i); try { myimap->updateImapKeywords(msgSet, newflags, true, *origCallback); origCallback=NULL; delete this; } catch (...) { delete this; } } void mail::imap::updateImapKeywordHelper::fail(std::string message) { mail::callback *c=origCallback; origCallback=NULL; delete this; c->fail(message); } void mail::imap::updateImapKeywordHelper ::reportProgress(size_t bytesCompleted, size_t bytesEstimatedTotal, size_t messagesCompleted, size_t messagesEstimatedTotal) { } void mail::imap::updateImapKeywords(const vector &messages, const set &keywords, bool changeTo, callback &cb) { set::const_iterator b=keywords.begin(), e=keywords.end(); string setCmd= changeTo ? " +FLAGS (":" -FLAGS ("; const char *space=""; while (b != e) { string flagname= *b; if (!keywordAllowed(flagname)) { vector::const_iterator mb, me; mb=messages.begin(); me=messages.end(); while (mb != me) { size_t n= *mb; ++mb; if (n >= (currentFolder ? currentFolder->index.size():0)) continue; if (changeTo) { if (!currentFolder->index[n] .keywords.addFlag(keywordHashtable, flagname)) { LIBMAIL_THROW(strerror(errno)); } } else { if (!currentFolder->index[n].keywords .remFlag(flagname)) LIBMAIL_THROW(strerror(errno)); } } ++b; continue; } setCmd += space; setCmd += *b; ++b; space=" "; } if (*space == 0) { MONITOR(mail::imap); vector::const_iterator mb, me; mb=messages.begin(); me=messages.end(); while (!DESTROYED() && mb != me) { size_t n= *--me; if (n >= (currentFolder ? currentFolder->index.size():0)) continue; currentFolder->folderCallback.messageChanged(n); } cb.success("Ok."); return; } mail::imapMessageCallbackStub *c= new mail::imapMessageCallbackStub(cb); if (!c) { cb.fail("Out of memory."); return; } try { messagecmd(messages, setCmd + ")", "UID STORE", "STORE", *c); } catch (...) { delete c; LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); } } /////////////////////////////////////////////////////////////////////////// void mail::imap::copyMessagesTo(const vector &messages, mail::folder *copyTo, mail::callback &callback) { sameServerFolderPtr=NULL; copyTo->sameServerAsHelperFunc(); if (!sameServerFolderPtr) { // Copy to some other server, use a generic function. mail::copyMessages::copy(this, messages, copyTo, callback); return; } if (smap) { installForegroundTask(new smapCOPY(messages, copyTo, *this, callback, "COPY")); return; } mail::imapMessageCallbackStub *c= new mail::imapMessageCallbackStub(callback); if (!c) { callback.fail("Out of memory."); return; } try { messagecmd(messages, " " + quoteSimple(copyTo->getPath()), "UID COPY", "COPY", *c); } catch (...) { delete c; LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); } } void mail::imap::moveMessagesTo(const vector &messages, mail::folder *copyTo, mail::callback &callback) { sameServerFolderPtr=NULL; copyTo->sameServerAsHelperFunc(); if (smap && sameServerFolderPtr) { installForegroundTask(new smapCOPY(messages, copyTo, *this, callback, "MOVE")); return; } generic::genericMoveMessages(this, messages, copyTo, callback); } /////////////////////////////////////////////////////////////////////////// // // The search command. LIBMAIL_START class imapSEARCH : public imapCommandHandler { string searchCmd; bool hasStringArg; string stringArg; mail::searchCallback &callback; vector found; class SearchResults : public imapHandlerStructured { vector &found; public: SearchResults(vector &); ~SearchResults(); void installed(imap &imapAccount); private: const char *getName(); void timedOut(const char *errmsg); void process(imap &imapAccount, Token t); }; public: imapSEARCH(string searchCmdArg, bool hasParam2, string param2Arg, mail::searchCallback &callbackArg); ~imapSEARCH(); void installed(imap &); const char *getName(); void timedOut(const char *errmsg); bool untaggedMessage(imap &imapAccount, string name); bool taggedMessage(imap &imapAccount, string name, string message, bool okfail, string errmsg); bool continuationRequest(imap &imapAccount, string request); }; LIBMAIL_END mail::imapSEARCH::imapSEARCH(string searchCmdArg, bool hasParam2, string param2Arg, mail::searchCallback &callbackArg) : searchCmd(searchCmdArg), hasStringArg(hasParam2), stringArg(param2Arg), callback(callbackArg) { } mail::imapSEARCH::~imapSEARCH() { } void mail::imapSEARCH::installed(mail::imap &imapAccount) { if (hasStringArg) { string::iterator b=stringArg.begin(), e=stringArg.end(); while (b != e) { if (*b < ' ' || *b >= 127) break; b++; } if (b == e) searchCmd += mail::imap::quoteSimple(stringArg); else { ostringstream o; o << "{" << stringArg.size() << "}"; searchCmd += o.str(); } } imapAccount.imapcmd("SEARCH", "SEARCH " + searchCmd + "\r\n"); } // // High-8 search string must be sent via a literal. // bool mail::imapSEARCH::continuationRequest(mail::imap &imapAccount, string request) { imapAccount.socketWrite(stringArg + "\r\n"); return true; } const char *mail::imapSEARCH::getName() { return "SEARCH"; } void mail::imapSEARCH::timedOut(const char *errmsg) { callback.fail(errmsg); } bool mail::imapSEARCH::untaggedMessage(mail::imap &imapAccount, string name) { if (name == "SEARCH") { imapAccount.installBackgroundTask(new SearchResults(found)); return true; } return false; } bool mail::imapSEARCH::taggedMessage(mail::imap &imapAccount, string name, string message, bool okfail, string errmsg) { if (name != "SEARCH") return false; if (okfail) callback.success(found); else callback.fail(errmsg); imapAccount.uninstallHandler(this); return true; } mail::imapSEARCH::SearchResults::SearchResults(vector &foundArg) : found(foundArg) { } mail::imapSEARCH::SearchResults::~SearchResults() { } void mail::imapSEARCH::SearchResults::installed(mail::imap &imapAccount) { } const char *mail::imapSEARCH::SearchResults::getName() { return "* SEARCH"; } void mail::imapSEARCH::SearchResults::timedOut(const char *errmsg) { } void mail::imapSEARCH::SearchResults::process(mail::imap &imapAccount, Token t) { if (t == EOL) return; if (t != ATOM) { error(imapAccount); return; } size_t n=0; istringstream i(t.text.c_str()); i >> n; if (n == 0 || imapAccount.currentFolder == NULL || n > imapAccount.currentFolder->index.size()) { error(imapAccount); return; } found.push_back(n-1); } void mail::imap::searchMessages(const class mail::searchParams &searchInfo, class mail::searchCallback &callback) { if (smap) { installForegroundTask(new mail::smapSEARCH(searchInfo, callback, *this)); return; } if (folderFlags.count("\\FLAGGED") == 0) { // Server doesn't implement \Flagged, do everything by hand mail::searchMessages::search(callback, searchInfo, this); return; } string cmd=""; if (searchInfo.charset.size() > 0) cmd="CHARSET " + quoteSimple(searchInfo.charset) + " "; switch (searchInfo.scope) { case searchParams::search_all: cmd += "ALL"; break; case searchParams::search_unmarked: cmd += "ALL NOT FLAGGED"; break; case searchParams::search_marked: cmd += "ALL FLAGGED"; break; case searchParams::search_range: { ostringstream o; o << searchInfo.rangeLo+1 << ':' << searchInfo.rangeHi; cmd += o.str(); } break; } bool notFlag=searchInfo.searchNot; if (searchInfo.criteria == searchInfo.unread) notFlag= !notFlag; if (notFlag) cmd += " NOT"; string sizeNumStr=""; switch (searchInfo.criteria) { case searchParams::larger: case searchParams::smaller: { unsigned long sizeNum=0; istringstream i(searchInfo.param2.c_str()); i >> sizeNum; if (i.fail()) { callback.fail("Invalid message size string."); return; } string buffer; { ostringstream o; o << sizeNum; buffer=o.str(); } sizeNumStr=buffer; } break; default: break; } string param2=""; bool hasParam2=false; string::const_iterator b, e; switch (searchInfo.criteria) { case searchParams::replied: cmd += " ANSWERED"; break; case searchParams::deleted: cmd += " DELETED"; break; case searchParams::draft: cmd += " DRAFT"; break; case searchParams::unread: cmd += " SEEN"; break; // Header match case searchParams::from: cmd += " FROM "; hasParam2=true; param2=searchInfo.param2; break; case searchParams::to: cmd += " TO "; hasParam2=true; param2=searchInfo.param2; break; case searchParams::cc: cmd += " CC "; hasParam2=true; param2=searchInfo.param2; break; case searchParams::bcc: cmd += " BCC "; hasParam2=true; param2=searchInfo.param2; break; case searchParams::subject: cmd += " SUBJECT "; hasParam2=true; param2=searchInfo.param2; break; case searchParams::header: b=searchInfo.param1.begin(); e=searchInfo.param1.end(); while (b != e) { if (*b < ' '|| *b >= 127) { callback.fail("Invalid header name."); return; } } cmd += " HEADER " + quoteSimple(searchInfo.param1) + " "; hasParam2=true; param2=searchInfo.param2; break; case searchParams::body: cmd += " BODY "; hasParam2=true; param2=searchInfo.param2; break; case searchParams::text: cmd += " TEXT "; hasParam2=true; param2=searchInfo.param2; break; // Internal date: case searchParams::before: cmd += " BEFORE "; hasParam2=true; param2=searchInfo.param2; break; case searchParams::on: cmd += " ON "; hasParam2=true; param2=searchInfo.param2; break; case searchParams::since: cmd += " SINCE "; hasParam2=true; param2=searchInfo.param2; break; // Sent date: case searchParams::sentbefore: cmd += " SENTBEFORE "; hasParam2=true; param2=searchInfo.param2; break; case searchParams::senton: cmd += " SENTON "; hasParam2=true; param2=searchInfo.param2; break; case searchParams::sentsince: cmd += " SENTSINCE "; hasParam2=true; param2=searchInfo.param2; break; case searchParams::larger: cmd += " LARGER " + sizeNumStr; break; case searchParams::smaller: cmd += " SMALLER " + sizeNumStr; break; default: callback.fail("Unknown search criteria."); return; } installForegroundTask(new mail::imapSEARCH(cmd, hasParam2, param2, callback)); } /////////////////////////////////////////////////////////////////////////// // // Convert flags in mail::messageInfo to IMAP flag names. // For those flags that are not supported on the server, update the // flags in the folder index manually, and set the flagsUpdated flag. string mail::imap::messageFlagStr(const mail::messageInfo &indexInfo, mail::messageInfo *oldIndexInfo, bool *flagsUpdated) { string flags=""; if (flagsUpdated) *flagsUpdated=false; #define DOFLAG(NOT, field, name) \ if (oldIndexInfo == NULL /* APPEND assumes all flags are present */ ||\ folderFlags.count( name ) > 0)\ {\ if (NOT indexInfo.field)\ flags += " " name;\ }\ else if ( oldIndexInfo->field != indexInfo.field)\ {\ oldIndexInfo->field=indexInfo.field;\ *flagsUpdated=true;\ } #define FLAG #define NOTFLAG ! LIBMAIL_MSGFLAGS; #undef FLAG #undef DOFLAG #undef NOTFLAG if (flags.size() > 0) flags=flags.substr(1); return flags; } //////////////////////////////////////////////////////////////////////////// // // The IMAP EXPUNGE command. LIBMAIL_START class imapEXPUNGE : public imapCommandHandler { mail::callback &callback; public: imapEXPUNGE(mail::callback &callbackArg); ~imapEXPUNGE(); void installed(imap &imapAccount); private: const char *getName(); void timedOut(const char *errmsg); bool untaggedMessage(imap &imapAccount, string name); bool taggedMessage(imap &imapAccount, string name, string message, bool okfail, string errmsg); }; LIBMAIL_END mail::imapEXPUNGE::imapEXPUNGE(mail::callback &callbackArg) : callback(callbackArg) { } void mail::imapEXPUNGE::timedOut(const char *errmsg) { callbackTimedOut(callback, errmsg); } mail::imapEXPUNGE::~imapEXPUNGE() { } void mail::imapEXPUNGE::installed(mail::imap &imapAccount) { imapAccount.imapcmd("EXPUNGE", "EXPUNGE\r\n"); } const char *mail::imapEXPUNGE::getName() { return "EXPUNGE"; } bool mail::imapEXPUNGE::untaggedMessage(mail::imap &imapAccount, string name) { return false; } bool mail::imapEXPUNGE::taggedMessage(mail::imap &imapAccount, string name, string message, bool okfail, string errmsg) { if (name == "EXPUNGE") { if (okfail) callback.success(errmsg); else callback.fail(errmsg); imapAccount.uninstallHandler(this); return true; } return false; } void mail::imap::updateNotify(bool enableDisable, callback &callbackArg) { if (!ready(callbackArg)) return; wantIdleMode=enableDisable; updateNotify(&callbackArg); } void mail::imap::updateNotify(callback *callbackArg) { if (!smap && (!hasCapability("IDLE") || noIdle)) { if (callbackArg) callbackArg->success("Ok"); return; } installForegroundTask(smap ? (imapHandler *)new smapIdleHandler(wantIdleMode, callbackArg): new imapIdleHandler(wantIdleMode, callbackArg)); } void mail::imap::updateFolderIndexInfo(mail::callback &callback) { if (!ready(callback)) return; installForegroundTask(smap ? (imapHandler *)new smapNoopExpunge("EXPUNGE", callback, *this) : new mail::imapEXPUNGE(callback)); } ////////////////////////////////////////////////////////////////////////// // // UID EXPUNGE //////////////////////////////////////////////////////////////////////////// // // The IMAP EXPUNGE command. LIBMAIL_START class imapUIDEXPUNGE : public mail::callback::message { set uids; ptr acct; mail::callback &callback; bool uidSent; public: imapUIDEXPUNGE(mail::imap &imapAccount, mail::callback &callbackArg, const vector &msgs); ~imapUIDEXPUNGE(); void mkVector(mail::imap &, vector &); private: void success(string msg); void fail(string msg); void reportProgress(size_t bytesCompleted, size_t bytesEstimatedTotal, size_t messagesCompleted, size_t messagesEstimatedTotal); }; LIBMAIL_END mail::imapUIDEXPUNGE::imapUIDEXPUNGE(mail::imap &imapAccount, mail::callback &callbackArg, const vector &msgs) : acct(&imapAccount), callback(callbackArg), uidSent(false) { vector::const_iterator b, e; for (b=msgs.begin(), e=msgs.end(); b != e; b++) { if (imapAccount.currentFolder && *b < imapAccount.currentFolder->index.size()) uids.insert(imapAccount.currentFolder ->index[*b].uid); } } mail::imapUIDEXPUNGE::~imapUIDEXPUNGE() { } void mail::imapUIDEXPUNGE::mkVector(mail::imap &imapAccount, vector &vec) { if (imapAccount.currentFolder) { size_t i; for (i=0; iexists; i++) { if (uids.count(imapAccount.currentFolder ->index[i].uid) > 0) vec.push_back(i); } } } void mail::imapUIDEXPUNGE::success(string dummy) { if (acct.isDestroyed() || uidSent) { callback.success(dummy); delete this; return; } vector vec; mkVector(*acct, vec); if (vec.size() == 0) { mail::callback * volatile c= &callback; delete this; c->fail(dummy); return; } uidSent=true; acct->messagecmd(vec, "", "UID EXPUNGE", "EXPUNGE", *this); } void mail::imapUIDEXPUNGE::reportProgress(size_t bytesCompleted, size_t bytesEstimatedTotal, size_t messagesCompleted, size_t messagesEstimatedTotal) { } void mail::imapUIDEXPUNGE::fail(string dummy) { mail::callback * volatile c= &callback; delete this; c->fail(dummy); } void mail::imap::removeMessages(const std::vector &messages, callback &cb) { if (!ready(cb)) return; if (smap) { installForegroundTask(new smapNoopExpunge(messages, cb, *this)); return; } if (!hasCapability("UIDPLUS")) { mail::generic::genericRemoveMessages(this, messages, cb); return; } imapUIDEXPUNGE *exp=new imapUIDEXPUNGE(*this, cb, messages); try { vector msgs; exp->mkVector(*this, msgs); if (msgs.size() == 0) { delete exp; exp=NULL; cb.success("OK"); return; } messagecmd(msgs, " +FLAGS.SILENT (\\Deleted)", "UID STORE", "STORE", *exp); } catch (...) { if (exp) delete exp; cb.fail("An exception occured in removeMessages"); return; } }