diff options
Diffstat (limited to 'libmail')
217 files changed, 56347 insertions, 0 deletions
diff --git a/libmail/.gitignore b/libmail/.gitignore new file mode 100644 index 0000000..3ffb273 --- /dev/null +++ b/libmail/.gitignore @@ -0,0 +1 @@ +/libmail_config.h.in diff --git a/libmail/Makefile.am b/libmail/Makefile.am new file mode 100644 index 0000000..882c6bb --- /dev/null +++ b/libmail/Makefile.am @@ -0,0 +1,189 @@ +# +# Copyright 2002-2009, Double Precision Inc. +# +# See COPYING for distribution information. +# + +CONFIG_STATUS_DEPENDENCIES=../tcpd/couriertls.config + +noinst_LTLIBRARIES=libmail.la +noinst_PROGRAMS=mailtool testsuite + +AM_CFLAGS = $(LIBIDN_CFLAGS) + +libmail_la_SOURCES=misc.H mail.H mail.C \ + addmessage.C addmessage.H \ + addmessageimport.C \ + addmessageremoveattachments.C \ + addressbook.H addressbook.C \ + addressbookadd.H addressbookadd.C \ + addressbookget.H addressbookget.C \ + addressbookopen.H addressbookopen.C \ + addressbooksearch.H addressbooksearch.C \ + attachments.C attachments.H \ + autodecoder.C autodecoder.H \ + envelope.C envelope.H \ + base64.C base64.H \ + copymessage.C copymessage.H \ + decoder.C decoder.H \ + driver.C driver.H \ + expungelist.C expungelist.H \ + fd.C fd.H \ + fdtls.C fdtls.H \ + file.C file.H \ + generic.C generic.H \ + genericdecode.C genericdecode.H \ + headers.C headers.H \ + imap.C imap.H \ + imapacl.C imapacl.H \ + imapidle.C imapidle.H \ + imaplogin.C imaplogin.H \ + imaplogout.C \ + imapfetchhandler.C imapfetchhandler.H \ + imaphandler.C imaphandler.H \ + imaphmac.C imaphmac.H \ + imapfolder.C imapfolder.H \ + imapfolders.C imapfolders.H \ + imaplisthandler.C imaplisthandler.H \ + imapparsefmt.C imapparsefmt.H \ + imapstatushandler.C imapstatushandler.H \ + logininfo.C logininfo.H \ + maildir.C maildir.H \ + maildiradd.C maildiradd.H \ + maildirfolder.C maildirfolder.H \ + mbox.C mbox.H \ + mboxadd.C mboxadd.H \ + mboxexpunge.C mboxexpunge.H \ + mboxfolder.C \ + mboxgetmessage.C mboxgetmessage.H \ + mboxlock.C mboxlock.H \ + mboxmagictag.C mboxmagictag.H \ + mboxmultilock.C mboxmultilock.H \ + mboxopen.C mboxopen.H \ + mboxread.C mboxread.H \ + mboxsighandler.C mboxsighandler.H \ + mimetypes.C mimetypes.H \ + namespace.H \ + nntp.C nntp.H \ + nntpadd.C nntpadd.H \ + nntpcache.C nntpcache.H \ + nntpchecknew.C nntpchecknew.H \ + nntpfetch.C nntpfetch.H \ + nntpfolder.C nntpfolder.H \ + nntpgroup.C nntpgroup.H \ + nntpgroupinfo.C nntpgroupinfo.H \ + nntpgroupopen.C nntpgroupopen.H \ + nntplistactive.C nntplistactive.H \ + nntplogin.C nntplogin.H \ + nntplogin2.C nntplogin2.H \ + nntplogout.C nntplogout.H \ + nntpnewsrc.C nntpnewsrc.H \ + nntppost.C nntppost.H \ + nntpxover.C nntpxover.H \ + nntpxpat.C nntpxpat.H \ + objectmonitor.C objectmonitor.H \ + poll.C \ + pop3.C pop3.H \ + pop3folder.C \ + pop3maildrop.C pop3maildrop.H \ + qp.C qp.H \ + rfc2047decode.C rfc2047decode.H \ + rfc2047encode.C rfc2047encode.H \ + rfcaddr.C rfcaddr.H \ + runlater.C runlater.H \ + search.C search.H \ + smap.C smap.H \ + smapacl.C smapacl.H \ + smapadd.C smapadd.H \ + smapaddmessage.C smapaddmessage.H \ + smapcopy.C smapcopy.H \ + smapcreate.C smapcreate.H \ + smapdelete.C smapdelete.H \ + smapfetch.C smapfetch.H \ + smapfetchattr.C smapfetchattr.H \ + smapidle.C smapidle.H \ + smaplist.C smaplist.H \ + smapmsgrange.C smapmsgrange.H \ + smapnewmail.C smapnewmail.H \ + smapnoopexpunge.C smapnoopexpunge.H \ + smapopen.C smapopen.H \ + smapsearch.C smapsearch.H \ + smapsendfolder.C smapsendfolder.H \ + smapstatus.C smapstatus.H \ + smapstore.C smapstore.H \ + smtp.C smtp.H \ + smtpfolder.H smtpfolder.C \ + smtpinfo.H \ + snapshot.C snapshot.H \ + sortfolders.C \ + structure.C structure.H \ + sync.C sync.H \ + tmpaccount.C tmpaccount.H \ + tmpaccountadd.C tmpaccountfolder.C +libmail_la_LIBADD = $(LIBIDN_LIBS) + +mailtool_SOURCES=mailtool.C +mailtool_DEPENDENCIES=libmail.la \ + ../rfc2045/librfc2045.la \ + ../rfc822/librfc822.la ../rfc822/libencode.la \ + ../maildir/libmaildir.la \ + @LIBCOURIERTLS@ \ + ../liblock/liblock.la ../unicode/libunicode.la \ + ../libhmac/libhmac.la ../md5/libmd5.la ../sha1/libsha1.la \ + ../numlib/libnumlib.la ../tcpd/libspipe.la ../soxwrap/libsoxwrap.a \ + ../soxwrap/soxlibs.dep + +mailtool_LDADD=libmail.la ../rfc2045/librfc2045.la \ + ../rfc822/librfc822.la ../rfc822/libencode.la \ + ../maildir/libmaildir.la \ + @LIBCOURIERTLS@ \ + ../liblock/liblock.la ../unicode/libunicode.la \ + ../libhmac/libhmac.la ../md5/libmd5.la ../sha1/libsha1.la \ + ../numlib/libnumlib.la ../tcpd/libspipe.la ../soxwrap/libsoxwrap.a \ + `cat ../maildir/maildir.libdeps ../soxwrap/soxlibs.dep` +mailtool_LDFLAGS=-static + +testsuite_SOURCES=testsuite.C +testsuite_DEPENDENCIES=$(mailtool_DEPENDENCIES) +testsuite_LDADD=$(mailtool_LDADD) +testsuite_LDFLAGS=-static + +EXTRA_DIST=autodeps.C testsuite.txt + +check-am: + ./testsuite | cmp -s - $(srcdir)/testsuite.txt + +install-data-hook: @INSTINCLUDES@ + +uninstall-local: @UNINSTINCLUDES@ + +install-includes: + set -x ; \ + source=autodeps.C object=autodeps.o libtool=no \ + depfile=autodeps.P tmpdepfile=autodeps.tP \ + $(CXXDEPMODE) $(depcomp) \ + $(CXXCOMPILE) -c -o autodeps.o `test -f 'autodeps.C' || echo '$(srcdir)/'`autodeps.C + rm -f autodeps.o + $(mkinstalldirs) $(DESTDIR)$(includedir)/libmail + for f in `sed 's/.*://' <autodeps.P | tr -d '\\\\' | tr ' ' '\\012' | sed '/^\//d;/autodeps.C/d' | sort | uniq` ; do \ + $(INSTALL_DATA) $$f $(DESTDIR)$(includedir)/libmail ; case $$f in \ + */*) \ + n=`echo $$f | sed 's:.*/::'` ; \ + f=`echo $$f | sed 's:/[^/]*$$::'` ;\ + f=`echo $$f | sed 's:.*/::'` ;\ + $(mkinstalldirs) $(DESTDIR)$(includedir)/libmail/$$f ;\ + mv -f $(DESTDIR)$(includedir)/libmail/$$n $(DESTDIR)$(includedir)/libmail/$$f ;\ + ;; \ + esac ; done + rm -f autodeps.P + +uninstall-includes: + rm -rf $(DESTDIR)$(includedir)/libmail + +BUILT_SOURCES=libcouriertls.h +DISTCLEANFILES=libcouriertls.h mimetypefiles.h + +libcouriertls.h: ../tcpd/couriertls.config + cp /dev/null libcouriertls.h ; . ../tcpd/couriertls.config ; \ + test "$$couriertls" != "" || exit 0 ; \ + echo '#define HAVE_LIBCOURIERTLS 1' >libcouriertls.h diff --git a/libmail/addmessage.C b/libmail/addmessage.C new file mode 100644 index 0000000..fe33dc2 --- /dev/null +++ b/libmail/addmessage.C @@ -0,0 +1,148 @@ +/* +** Copyright 2002-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "addmessage.H" +#include "attachments.H" +#include <cstring> +#include <errno.h> + +// Default method implementation. + +mail::addMessage::addMessage(mail::account *ptr) : mail::ptr<mail::account>(ptr), + messageDate(0) +{ +} + +bool mail::addMessage::checkServer() +{ + if (isDestroyed()) + { + fail("Server connection closed."); + return false; + } + + return true; +} + +mail::addMessage::~addMessage() +{ +} + +mail::addMessagePull::addMessagePull() : messageDate(0) +{ +} + +mail::addMessagePull::~addMessagePull() +{ +} + +// +// Default MIME assembly implementation +// + +void mail::addMessage::assembleContent(size_t &handleRet, + const mail::Attachment &a, + mail::callback &cb) +{ + att_list.push_back(a); + + std::list<mail::Attachment>::iterator i=att_list.end(); + att_list_vec.push_back(--i); + handleRet=att_list_vec.size()-1; + cb.success("Ok."); +} + +void mail::addMessage::assembleMessageRfc822(size_t &handleRet, + std::string headers, + size_t n, + mail::callback &cb) +{ + if (n >= att_list_vec.size()) + { + errno=EINVAL; + throw strerror(errno); + } + + std::vector<mail::Attachment *> a; + + a.push_back(&*att_list_vec[n]); + + assembleContent(handleRet, Attachment(headers, a), cb); +} + +void mail::addMessage::assembleMultipart(size_t &handleRet, + std::string headers, + const std::vector<size_t> &atts, + std::string type, + const mail::mimestruct + ::parameterList &typeParams, + mail::callback &cb) +{ + std::vector<Attachment *> parts; + std::vector<size_t>::const_iterator b=atts.begin(), e=atts.end(); + + while (b != e) + { + if (*b >= att_list_vec.size()) + { + errno=EINVAL; + throw strerror(errno); + } + + parts.push_back(&*att_list_vec[*b]); + ++b; + } + + assembleContent(handleRet, Attachment(headers, parts, type, + typeParams), cb); +} + +bool mail::addMessage::assemble() +{ + std::vector<std::list<mail::Attachment>::iterator>::const_iterator + e=att_list_vec.end(); + + if (e == att_list_vec.begin()) + return true; + + --e; + + (*e)->begin(); + + bool errflag=false; + + std::string msg; + + while ((msg=(*e)->generate(errflag)).size() > 0) + { + saveMessageContents(msg); + } + + if (errflag) + return false; + return true; +} + +bool mail::addMessage::chkMsgNum(mail::account *ptr, std::string msgUid, + size_t &n) +{ + size_t msgCount=ptr->getFolderIndexSize(); + size_t i; + + if (n < msgCount && ptr->getFolderIndexInfo(n).uid == msgUid) + return true; + for (i=1; i<msgCount; i++) + { + size_t j= (n+msgCount-i) % msgCount; + + if (ptr->getFolderIndexInfo(j).uid == msgUid) + { + n=j; + return true; + } + } + return false; +} diff --git a/libmail/addmessage.H b/libmail/addmessage.H new file mode 100644 index 0000000..1d01caf --- /dev/null +++ b/libmail/addmessage.H @@ -0,0 +1,139 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_addmessage_H +#define libmail_addmessage_H + +#include "mail.H" + +#include <string> +#include <list> +#include <vector> +#include <time.h> + +#include "objectmonitor.H" +#include "namespace.H" +#include "structure.H" + +LIBMAIL_START + +class Attachment; + +//////////////////////////////////////////////////////////////////////////// +// +// Add message to a folder. Each mail account is expected to create a +// subclass of mail::addMessage. A mail::addMessage object gets created by +// mail::folder::addMessage(). This superclass provides fields, initialized +// with defaults. The fields may be changed at any time before invoking the +// go() method. The message text is provided by calling saveMessageContents(). +// saveMessageContents() may be called repeatedly to provide the contents of +// a large message in pieces. The message is saved by the go() method. +// The process may be aborted at any time, prior to go(), by invoking fail(), +// which automatically destroys the mail::addMessage object. +// +// A failure occuring while trying to add the message (including invoking +// fail()) is reported by the callback's fail method (the subclass receives +// the callback object and is responsible for dispatching the proper +// notification). + + +class addMessage : private ptr<mail::account> { + +protected: + bool checkServer(); + +public: + addMessage(mail::account *); + virtual ~addMessage(); + + mail::messageInfo messageInfo; // Message flags + + time_t messageDate; // Message add date. + + virtual void saveMessageContents(std::string)=0; + virtual void go()=0; + virtual void fail(std::string errmsg)=0; + + // Default MIME composition implementation + +protected: + std::list<mail::Attachment> att_list; + std::vector< std::list<mail::Attachment>::iterator > att_list_vec; + +public: + virtual void assembleContent(size_t &, + const mail::Attachment &, + mail::callback &); + virtual void assembleMessageRfc822(size_t &, std::string, size_t, + mail::callback &); + virtual void assembleMultipart(size_t &, + std::string, + const std::vector<size_t> &, + std::string, + const mail::mimestruct::parameterList &, + mail::callback &); + void assembleMultipart(size_t &handleRet, + std::string headers, + const std::vector<size_t> &parts, + std::string multipart_type, + mail::callback &cb) + { + mail::mimestruct::parameterList dummy; + + return assembleMultipart(handleRet, + headers, parts, multipart_type, dummy, + cb); + } + + virtual void assembleImportAttachment(size_t &handleRet, + mail::account *acct, + std::string msgUid, + const mail::mimestruct &attachment, + mail::callback &cb); + class assembleImportHelper; + + virtual void assembleRemoveAttachmentsFrom(size_t &handleRet, + mail::account *acct, + std::string msgUid, + const mail::mimestruct + &msgStruct, + const std::set<std::string> + &removeUidList, + mail::callback &cb); + class assembleRemoveAttachmentsHelper; + + virtual bool assemble(); + +private: + static bool chkMsgNum(mail::account *ptr, std::string msgUid, + size_t &n); + + +}; + +// addMessage is a "push" interface - the application "pushes" +// the new message's contents via saveMessageContents(). The mail::ACCOUNT +// API provides an alternative "pull" implementation, where a +// addMessagePull object is passed instead, whose getMessageContents() +// method is repeatedly invoked. getMessageContents() should return the +// next part of the message's contents. getMessageContents() is called +// repeatedly, until it returns an empty string which signifies end of +// message contents. + +class addMessagePull { +public: + addMessagePull(); + virtual ~addMessagePull(); + + mail::messageInfo messageInfo; // Message flags + time_t messageDate; // Message add date. + + virtual std::string getMessageContents()=0; +}; + +LIBMAIL_END + +#endif + diff --git a/libmail/addmessageimport.C b/libmail/addmessageimport.C new file mode 100644 index 0000000..9027545 --- /dev/null +++ b/libmail/addmessageimport.C @@ -0,0 +1,194 @@ +/* +** Copyright 2002-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "addmessage.H" +#include "attachments.H" +#include <errno.h> +#include <cstring> + +using namespace std; + +class mail::addMessage::assembleImportHelper : public callback::message { + + mail::addMessage *addMessagePtr; + size_t &handleRet; + mail::ptr<mail::account> acct; + string msgUid; + size_t msgNum; + const mail::mimestruct &attachment; + mail::callback &cb; + FILE *tmpfp; + + string headers; + + // Imported from mail::callback::message: + void messageTextCallback(size_t n, std::string text); + + // Imported from mail::callback: + void success(std::string message); + void fail(std::string message); + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + size_t messagesCompleted, + size_t messagesEstimatedTotal); + +public: + assembleImportHelper(mail::addMessage *addMessagePtr, + size_t &handleRetArg, + mail::account *acctArg, + std::string msgUidArg, + size_t msgNumArg, + const mail::mimestruct &attachmentArg, + mail::callback &cbArg); + ~assembleImportHelper(); + void go(); +}; + +void mail::addMessage::assembleImportAttachment(size_t &handleRet, + mail::account *acct, + std::string msgUid, + const mail::mimestruct &attachment, + mail::callback &cb) +{ + size_t n=0; + mail::addMessage::assembleImportHelper *h; + + if (!mail::addMessage::chkMsgNum(acct, msgUid, n)) + { + cb.fail("Message not found."); + return; + } + + h=new mail::addMessage::assembleImportHelper(this, + handleRet, acct, + msgUid, n, attachment, + cb); + + if (!h) + { + cb.fail(strerror(errno)); + return; + } + + try + { + h->go(); + } catch (...) { + delete h; + cb.fail(strerror(errno)); + } +} + +mail::addMessage::assembleImportHelper +::assembleImportHelper(mail::addMessage *addMessagePtrArg, + size_t &handleRetArg, + mail::account *acctArg, + std::string msgUidArg, + size_t msgNumArg, + const mail::mimestruct &attachmentArg, + mail::callback &cbArg) + : addMessagePtr(addMessagePtrArg), + handleRet(handleRetArg), + acct(acctArg), + msgUid(msgUidArg), + msgNum(msgNumArg), + attachment(attachmentArg), + cb(cbArg), + tmpfp(NULL) +{ +} + +mail::addMessage::assembleImportHelper::~assembleImportHelper() +{ + if (tmpfp) + fclose(tmpfp); +} + +void mail::addMessage::assembleImportHelper::go() +{ + acct->readMessageContent(msgNum, true, attachment, + mail::readHeaders, + *this); + +} + +void mail::addMessage::assembleImportHelper::messageTextCallback(size_t n, + string text) +{ + if (tmpfp == NULL) + { + // Reading headers. + headers += text; + } + else + { + // Reading content + if (fwrite(text.c_str(), text.size(), 1, tmpfp) != 1) + ; // Supress warning + } +} + +void mail::addMessage::assembleImportHelper::success(string message) +{ + if (tmpfp == NULL) // Just read headers, check for error, read body. + { + if (acct.isDestroyed() || !chkMsgNum(acct, msgUid, msgNum)) + { + fail("Message not found."); + return; + } + if ((tmpfp=tmpfile()) == NULL) + { + fail(strerror(errno)); + return; + } + + acct->readMessageContentDecoded(msgNum, true, attachment, + *this); + return; + } + + // Just read the content. + + if (fseek(tmpfp, 0L, SEEK_SET) < 0 || ferror(tmpfp)) + { + fail(strerror(errno)); + return; + } + + string::iterator hstart, hend; + + string encoding= + mail::Attachment::find_header("CONTENT-TRANSFER-ENCODING:", + headers, hstart, hend); + + if (hstart != hend) + headers.erase(hstart, hend); + + addMessagePtr-> + assembleContent(handleRet, + mail::Attachment(headers, fileno(tmpfp), "", + encoding), cb); + delete this; +} + +void mail::addMessage::assembleImportHelper::fail(string message) +{ + cb.fail(message); + delete this; +} + +void mail::addMessage::assembleImportHelper::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + cb.reportProgress(bytesCompleted, + bytesEstimatedTotal, + messagesCompleted, + messagesEstimatedTotal); +} diff --git a/libmail/addmessageremoveattachments.C b/libmail/addmessageremoveattachments.C new file mode 100644 index 0000000..650058e --- /dev/null +++ b/libmail/addmessageremoveattachments.C @@ -0,0 +1,404 @@ +/* +** Copyright 2004-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "addmessage.H" +#include "attachments.H" +#include "headers.H" +#include <errno.h> +#include <vector> +#include <cstring> + +using namespace std; + +class mail::addMessage::assembleRemoveAttachmentsHelper + : public mail::callback::message { + +public: + void doNextCopy(std::string message); + + +private: + // Imported from mail::callback: + 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 messageTextCallback(size_t n, std::string text); + + size_t &handleRet; + mail::ptr<mail::account> acct; + string msgUid; + size_t msgNum; + + mail::addMessage *add; + mail::callback &origCallback; + + void (assembleRemoveAttachmentsHelper::*success_func)(std::string); + void doMultiPart(std::string message); + + string collectedHeaders; + +public: + assembleRemoveAttachmentsHelper(size_t &handleRetArg, + mail::account *acctArg, + string msgUidArg, + mail::addMessage *addArg, + mail::callback &origCallbackArg) + : handleRet(handleRetArg), + acct(acctArg), + msgUid(msgUidArg), + msgNum(0), + add(addArg), + origCallback(origCallbackArg) + { + } + + ~assembleRemoveAttachmentsHelper() {} + + + // We get a message's MIME structure, and the MIME ids of attachments + // to remove. Create a tree structure for each MIME section with + // two data points: + // + // A) Whether or not either this MIME section should be copied, or + // at least one of the MIME subsections should be copied + // + // B) Whether or not either this MIME section should not be copied, or + // at least one of the MIME subsections should not be copied + // + class mimeAction; + + class mimeAction { + public: + const mail::mimestruct §ion; + + bool somethingCopied; + bool somethingNotCopied; + + // + // The list of MIME actions is built into a list + // (so we don't need to manually allocate these objects), + // and the MIME subsections are saved in the following + // vector of iterators to the subsection of this section + + vector< list<mimeAction>::iterator > subsections; + size_t handleId; + + mimeAction(const mail::mimestruct §ionArg, bool a, bool b) + : section(sectionArg), somethingCopied(a), + somethingNotCopied(b) {} + + ~mimeAction() {} + }; + + list<mimeAction> parseTree; + + list<mimeAction>::iterator + computeToCopy(const mail::mimestruct §ion, + const set<string> &whatNotToCopy); // Compute parseTree + + // + // Now, iterate over the parse tree, and create the execution plan + // of things to copy. + // + + class plan { + public: + bool multiPartCopy; + list<mimeAction>::iterator whichPart; + + plan(bool multipartCopyArg, + list<mimeAction>::iterator whichPartArg) + : multiPartCopy(multipartCopyArg), + whichPart(whichPartArg) + { + } + + ~plan() {} + }; + + + vector< plan > copyPlan; + vector< plan >::iterator ToDo; + + void computePlan( list<mimeAction>::iterator ); +}; + +#define MIMEACTION mail::addMessage::assembleRemoveAttachmentsHelper::mimeAction + +list<MIMEACTION>::iterator +mail::addMessage::assembleRemoveAttachmentsHelper +::computeToCopy(const mail::mimestruct §ion, + const set<string> &whatNotToCopy) +{ + list<MIMEACTION>::iterator p; + + if (whatNotToCopy.count(section.mime_id) > 0) + { + parseTree.push_back(MIMEACTION(section, false, true)); + p=parseTree.end(); + return --p; + } + + size_t n=section.getNumChildren(); + + if (n == 0) + { + // Copy this one, by default + + parseTree.push_back(MIMEACTION(section, true, false)); + p=parseTree.end(); + return --p; + } + + vector< list<MIMEACTION>::iterator > subsections; + + size_t i; + + bool somethingCopied=false; + bool somethingNotCopied=false; + + for (i=0; i<n; i++) + { + p=computeToCopy(*section.getChild(i), whatNotToCopy); + + if (p->somethingNotCopied) + somethingNotCopied=true; + + if (!p->somethingCopied) + continue; + + somethingCopied=true; + subsections.push_back(p); + } + + parseTree.push_back(MIMEACTION(section, somethingCopied, + somethingNotCopied)); + + p=parseTree.end(); + --p; + + p->subsections=subsections; + return p; +} + + +void mail::addMessage::assembleRemoveAttachmentsHelper +::computePlan(list<MIMEACTION>::iterator nodeArg) +{ + if (!nodeArg->somethingCopied) + return; // Skip this entire MIME section + + if (!nodeArg->somethingNotCopied) + { + copyPlan.push_back(plan(false, nodeArg)); + // Copy this entire section, multipart or not + return; + } + + size_t i, n= nodeArg->subsections.size(); + + for (i=0; i<n; i++) + computePlan(nodeArg->subsections[i]); + + copyPlan.push_back(plan(true, nodeArg)); +} + +void mail::addMessage +::assembleRemoveAttachmentsFrom(size_t &handleRetArg, + mail::account *acctArg, + string msgUidArg, + const mail::mimestruct &msgStructArg, + const set<string> &removeUidListArg, + mail::callback &cbArg) +{ + assembleRemoveAttachmentsHelper *helper= + new assembleRemoveAttachmentsHelper(handleRetArg, + acctArg, + msgUidArg, + this, + cbArg); + + if (!helper) + { + cbArg.fail(strerror(errno)); + return; + } + + try { + list<MIMEACTION>::iterator topNode; + + topNode=helper->computeToCopy(msgStructArg, removeUidListArg); + + if (!topNode->somethingCopied) + { + delete helper; + helper=NULL; + cbArg.fail("Cannot remove every attachment."); + return; + } + + if (!topNode->somethingNotCopied) + { + delete helper; + helper=NULL; + cbArg.fail("No attachments selected for deletion"); + return; + } + helper->computePlan(topNode); + helper->ToDo=helper->copyPlan.begin(); + helper->doNextCopy("Ok."); // Kick into action. + } catch (...) + { + if (helper) + delete helper; + cbArg.fail(strerror(errno)); + } +} + +void mail::addMessage +::assembleRemoveAttachmentsHelper::success(std::string message) +{ + (this->*success_func)(message); +} + +void mail::addMessage +::assembleRemoveAttachmentsHelper::doNextCopy(std::string message) +{ + origCallback.reportProgress(0, 0, ToDo-copyPlan.begin()+1, + copyPlan.size()); + + if (ToDo == copyPlan.end()) + { + try { + --ToDo; + handleRet=ToDo->whichPart->handleId; + + origCallback.success(message); + } catch (...) + { + delete this; + throw; + } + delete this; + return; + } + + if (acct.isDestroyed()) + { + fail("Server connection terminated."); + return; + } + + if (!chkMsgNum(acct, msgUid, msgNum)) + { + fail("Message deleted in the middle of being copied."); + return; + } + + plan &toDoNext= *ToDo; + mimeAction &toDoInfo= *toDoNext.whichPart; + + if (toDoNext.multiPartCopy) + { + collectedHeaders=""; + success_func= &assembleRemoveAttachmentsHelper::doMultiPart; + + acct->readMessageContent(msgNum, true, + toDoInfo.section, + readHeaders, *this); + return; + } + + ++ToDo; + success_func= &assembleRemoveAttachmentsHelper::doNextCopy; + + add->assembleImportAttachment(toDoInfo.handleId, acct, msgUid, + toDoInfo.section, + *this); +} + +void mail::addMessage +::assembleRemoveAttachmentsHelper::doMultiPart(std::string message) +{ + if (acct.isDestroyed()) + { + fail("Server connection terminated."); + return; + } + + plan &toDoNext= *ToDo; + mimeAction &toDoInfo= *toDoNext.whichPart; + + vector<size_t> parts; + + vector< list<mimeAction>::iterator >::iterator b, e; + + for (b=toDoInfo.subsections.begin(), + e=toDoInfo.subsections.end(); b != e; ++b) + parts.push_back( (*b)->handleId ); + + string::iterator hstart, hend; + + string encoding= + mail::Attachment::find_header("CONTENT-TRANSFER-ENCODING:", + collectedHeaders, hstart, hend); + + if (hstart != hend) + collectedHeaders.erase(hstart, hend); + + string content_type_header= + mail::Attachment::find_header("CONTENT-TYPE:", + collectedHeaders, hstart, hend); + + mail::Header::mime ct= + mail::Header::mime::fromString(string(hstart, hend)); + if (hstart != hend) + collectedHeaders.erase(hstart, hend); + + ++ToDo; + success_func= &assembleRemoveAttachmentsHelper::doNextCopy; + + add->assembleMultipart(toDoInfo.handleId, collectedHeaders, + parts, + ct.value, + ct.parameters, + *this); +} + +void mail::addMessage +::assembleRemoveAttachmentsHelper::fail(std::string message) +{ + try { + origCallback.fail(strerror(errno)); + } catch (...) + { + delete this; + throw; + } + delete this; +} + +void mail::addMessage +::assembleRemoveAttachmentsHelper::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + origCallback.reportProgress(bytesCompleted, bytesEstimatedTotal, + ToDo-copyPlan.begin(), + copyPlan.size()); +} + +void mail::addMessage +::assembleRemoveAttachmentsHelper::messageTextCallback(size_t n, + std::string text) +{ + collectedHeaders += text; +} diff --git a/libmail/addressbook.C b/libmail/addressbook.C new file mode 100644 index 0000000..ca62ffc --- /dev/null +++ b/libmail/addressbook.C @@ -0,0 +1,296 @@ +/* +** Copyright 2002-2011, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include <cstring> + +#include "addressbook.H" +#include "addressbookadd.H" +#include "addressbookget.H" +#include "addressbookopen.H" +#include "addressbooksearch.H" +#include "rfc2047decode.H" +#include "misc.H" +#include <errno.h> + +using namespace std; + +mail::addressbook::Entry::Entry() +{ +} + +mail::addressbook::Entry::~Entry() +{ +} + +mail::addressbook::addressbook(mail::account *serverArg, + mail::folder *folderArg) + : server(serverArg), + folder(folderArg) +{ +} + +mail::addressbook::~addressbook() +{ +} + +void mail::addressbook::addEntry(Entry &newEntry, string olduid, + mail::callback &callback) +{ + vector<Index>::iterator b=index.begin(), e=index.end(); + + while (b != e) + { + Index &i= *b++; + + if (olduid.size() > 0 && i.uid == olduid) + continue; + + if (newEntry.nickname == i.nickname) + { + callback.fail("Address book entry with the same name already exists."); + return; + } + } + + Add *add=new Add(this, newEntry, olduid, callback); + + try { + add->go(); + } catch (...) { + delete add; + } +} + +void mail::addressbook::importEntries(std::list<Entry> &newEntries, + mail::callback &callback) +{ + Add *add=new Add(this, newEntries, callback); + + try { + add->go(); + } catch (...) { + delete add; + } +} + +// Delete an address book entry. + +void mail::addressbook::del(std::string uid, mail::callback &callback) +{ + vector<Index>::iterator b=index.begin(), e=index.end(); + + while (b != e) + { + if (b->uid == uid) + { + vector<size_t> msgList; + + msgList.push_back(b - index.begin()); + + server->removeMessages(msgList, callback); + return; + } + + b++; + } + + callback.success("OK"); +} + +void mail::addressbook::open(mail::callback &callback) +{ + Open *o=new Open(this, callback); + + try { + o->go(); + } catch (...) { + delete o; + } +} + +mail::addressbook::Index::Index() : nickname("(corrupted entry)") +{ +} + +mail::addressbook::Index::~Index() +{ +} + +void mail::addressbook::setIndex(size_t messageNumber, + string subject) +{ + subject= mail::rfc2047::decoder().decode(subject, "utf-8"); + + if (messageNumber < index.size()) + { + Index newEntry; + + size_t i=subject.find('['); + + if (i != std::string::npos) + { + subject=subject.substr(i+1); + + i=subject.find(']'); + + if (i != std::string::npos) + { + newEntry.nickname=subject.substr(0, i); + if (newEntry.nickname.size() == 0) + newEntry.nickname="(none)"; + + char *p=libmail_u_convert_tobuf(newEntry + .nickname + .c_str(), + "utf-8", + unicode_default_chset(), + NULL); + + if (!p) + LIBMAIL_THROW(strerror(errno)); + + try { + newEntry.nickname=p; + free(p); + } catch (...) { + free(p); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + } + } + + newEntry.uid=server->getFolderIndexInfo(messageNumber).uid; + index[messageNumber]=newEntry; + } +} + +void mail::addressbook::newMessages() +{ +} + +void mail::addressbook::messagesRemoved(vector< pair<size_t, size_t> > &r) +{ + vector< pair<size_t, size_t> >::iterator b=r.begin(), e=r.end(); + + while (b != e) + { + --e; + + size_t from=e->first, to=e->second; + + if (from >= index.size()) + continue; + + if (to > index.size()) + to=index.size(); + + index.erase(index.begin() + from, index.begin() + to + 1); + } +} + +void mail::addressbook::messageChanged(size_t n) +{ +} + +void mail::addressbook::getIndex( list< pair<string, std::string> > &listArg, + mail::callback &callback) +{ + size_t n=index.size(); + size_t i; + + for (i=0; i<n; i++) + { + string nickname=index[i].nickname; + string uid=index[i].uid; + + if (nickname.size() > 0 && uid.size() > 0) + listArg.push_back( make_pair(nickname, uid)); + } + + callback.success("Address book index retrieved"); +} + +template<class T> +void mail::addressbook::searchNickname( string nickname, + vector<T> &addrListArg, + mail::callback &callback) +{ + Search<T> *s=new Search<T>(this, addrListArg, callback); + + if (!s) + { + callback.fail(strerror(errno)); + return; + } + + try { + size_t n=index.size(); + size_t i; + + for (i=0; i<n; i++) + if (index[i].nickname == nickname) + s->uidList.push_back(server-> + getFolderIndexInfo(i) + .uid); + s->go(); + } catch (...) { + delete s; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +template +void mail::addressbook::searchNickname( string nickname, + vector<mail::address> &addrListArg, + mail::callback &callback); +template +void mail::addressbook::searchNickname( string nickname, + vector<mail::emailAddress> &addrListArg, + mail::callback &callback); + +template<class T> +void mail::addressbook::getEntry( string uid, + vector<T> &addrListArg, + mail::callback &callback) +{ + size_t n=index.size(); + size_t i; + + for (i=0; i<n; i++) + if (server->getFolderIndexInfo(i).uid == uid) + { + GetAddressList<T> *get= + new GetAddressList<T>(this, i, + addrListArg, + callback); + + if (!get) + { + callback.fail(strerror(errno)); + return; + } + + try { + get->go(); + } catch (...) { + delete get; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + return; + } + + callback.success("NOT FOUND."); +} + +template +void mail::addressbook::getEntry( string uid, + vector<mail::address> &addrListArg, + mail::callback &callback); +template +void mail::addressbook::getEntry( string uid, + vector<mail::emailAddress> + &addrListArg, + mail::callback &callback); diff --git a/libmail/addressbook.H b/libmail/addressbook.H new file mode 100644 index 0000000..6f85c6d --- /dev/null +++ b/libmail/addressbook.H @@ -0,0 +1,159 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_addressbook_H +#define libmail_addressbook_H + +#include "libmail_config.h" + +#include "mail.H" +#include "namespace.H" +#include "objectmonitor.H" + +#include <string> +#include <list> +#include <vector> + +LIBMAIL_START + +class address; +class emailAddress; + +///////////////////////////////////////////////////////////////////////////// +// +// Turn any folder into an address book. +// +// Create a mail::account object and a mail::folder object, then use this +// object to create an address book-type interface to them. +// +// This is implemented by saving each address book entry as a message in +// the folder. The message contains of a minimum set of mail headers. +// The Subject: header contains the address book nickname/handle. +// The address book entry is saved in a text/x-libmail-addressbok MIME section. +// There's also a brief text/plain section that informs the human reader to +// stay out of this folder and message. +// +// At this time, text/x-libmail-addressbok MIME section itself consists of a +// single Address: header (folded in the similar fashion as real mail headers), +// containing an RFC 2822-formatted address list, that uses the UTF-8 +// character set. + +class addressbook : public mail::obj, + private mail::callback::folder { + + // + // Handle folder update callbacks. + // + + void newMessages(); + void messagesRemoved(std::vector< std::pair<size_t, size_t> > &); + void messageChanged(size_t n); + + mail::account *server; + mail::folder *folder; + // + // The address book is here. + // + // server may NOT be used for any other purposes, by the application. + // + // server and folder may NOT be destroyed until this object is + // destroyed first. + + // + // The address book index. + // + + class Index { + public: + Index(); + ~Index(); + + std::string nickname; // Nickname, in app's charset + std::string uid; // Nickname uid. + }; + + std::vector<Index> index; + + class Add; + class Del; + class GetIndex; + class Open; + + template<class T> class GetAddressList; + template<class T> class Search; + + // + // Message #messageNumber has the following subject. + // The subject line stores the nickname, decode it. + // + // The subject line uses the UTF-8 character set, and contains the + // nickname inside brackets. setIndex() parses it out. + // + + void setIndex(size_t messageNumber, std::string subject); + +public: + friend class Add; + friend class Del; + friend class GetIndex; + friend class Open; + + friend class Search<mail::address>; + friend class Search<mail::emailAddress>; + + friend class GetAddressList<mail::address>; + friend class GetAddressList<mail::emailAddress>; + + class Entry { + public: + Entry(); + ~Entry(); + + std::string nickname; // Local charset + + std::vector<mail::emailAddress> addresses; + }; + + addressbook(mail::account *serverArg, + mail::folder *folderArg); + ~addressbook(); + + // Open the address book. Read the folder's contents, and initialize + // the address book index. + void open(mail::callback &callback); + + // Add a new address book entry. + void addEntry(Entry &newEntry, std::string olduid, + mail::callback &callback); + + void importEntries(std::list<Entry> &newEntries, + mail::callback &callback); + + // Delete an address book entry. + void del(std::string uid, mail::callback &callback); + + // Initialize listArg with the contents of the address book. + // listArg is a pair of nickname/uid. + void getIndex( std::list< std::pair<std::string, std::string> > + &listArg, mail::callback &callback); + + // Read address book entry #uid. Addresses are added to addrListArg. + template<class T> + void getEntry( std::string uid, + std::vector<T> &addrListArg, + mail::callback &callback); + + // Search the address book for the nickname. Initialize addrListArg + // if found. + template<class T> + void searchNickname(std::string nickname, + std::vector<T> &addrListArg, + mail::callback &callback); +}; + +LIBMAIL_END + +#endif + diff --git a/libmail/addressbookadd.C b/libmail/addressbookadd.C new file mode 100644 index 0000000..9110dec --- /dev/null +++ b/libmail/addressbookadd.C @@ -0,0 +1,266 @@ +/* +** Copyright 2002-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "addressbookadd.H" +#include "rfc2047encode.H" +#include "attachments.H" +#include "headers.H" +#include "envelope.H" +#include "rfcaddr.H" +#include "misc.H" +#include "unicode/unicode.h" +#include <ctype.h> +#include <vector> +#include <errno.h> +#include <cstring> + +using namespace std; + +mail::addressbook::Add::Add(mail::addressbook *addressBookArg, + mail::addressbook::Entry entryArg, + string oldUidArg, + mail::callback &callbackArg) + : addressBook(addressBookArg), + oldUid(oldUidArg), + callback(callbackArg), + addMessage(NULL) +{ + totCnt=1; + currentNum=0; + newEntries.push_back(entryArg); +} + +mail::addressbook::Add::Add(mail::addressbook *addressBookArg, + std::list<mail::addressbook::Entry> &entries, + mail::callback &callbackArg) + : addressBook(addressBookArg), + callback(callbackArg), + addMessage(NULL) +{ + newEntries.insert(newEntries.end(), + entries.begin(), + entries.end()); + totCnt=newEntries.size(); + currentNum=0; +} + +void mail::addressbook::Add::go() +{ + callback.reportProgress(0, 0, currentNum, totCnt); + if (newEntries.empty()) + { + successFunc= &mail::addressbook::Add::checked; + addressBook->server->checkNewMail( *this ); + return; + } + + multipart_params.clear(); + + mail::addressbook::Entry &newEntry=newEntries.front(); + + nickname=toutf8(newEntry.nickname); + + string::iterator b=nickname.begin(), e=nickname.end(); + + while (b != e) + { + char c= *b++; + + if ( (int)(unsigned char)c < ' ' || c == '[' || c == ']') + { + fail("Invalid address book nickname."); + return; + } + } + + if (newEntry.addresses.size() == 0) + { + fail("Invalid address."); + return; + } + + mail::addMessage *addp= + addressBook->folder->addMessage(*this); + + if (!addp) + return; + + time(&addp->messageDate); + + addMessage=addp; + + successFunc= &mail::addressbook::Add::addedIntro; + + multipart_params.push_back(0); + + mail::Header::list headers; + + headers << mail::Header::mime("Content-Type", "text/plain") + ("charset", "utf-8"); + + + mail::Attachment intro(headers, + "This message is used to store Libmail's" + " address book. Please do not modify\n" + "this folder, and message!\n", + "utf-8"); + + addMessage->assembleContent(multipart_params.end()[-1], intro, + *this); +} + +void mail::addressbook::Add::addedIntro(string successMsg) +{ + mail::Header::list headers; + mail::addressbook::Entry &newEntry=newEntries.front(); + + headers << mail::Header::plain("Content-Type", + "text/x-libmail-addressbook"); + + + mail::Attachment addresses(headers, "VERSION: 2\n" + + mail::address::toString("Address: ", + newEntry.addresses) + + "\n", "utf-8", "8bit"); + + successFunc= &mail::addressbook::Add::addedBeef; + + multipart_params.push_back(0); + + addMessage->assembleContent(multipart_params.end()[-1], addresses, + *this); +} + +void mail::addressbook::Add::addedBeef(string successMsg) +{ + // Assemble the multipart message. + + mail::Header::list headers; + + vector<mail::emailAddress> from_addresses; + + from_addresses.push_back(mail::address("Libmail Address Book", + "libmail@localhost")); + + headers << mail::Header::addresslist("From", from_addresses); + headers << mail::Header::encoded("Subject", + "[" + nickname + "]", + "utf-8"); + + successFunc= &mail::addressbook::Add::addedAll; + addMessage->assembleMultipart(dummyRet, headers, multipart_params, + "multipart/mixed", *this); +} + +void mail::addressbook::Add::addedAll(string successMsg) +{ + newEntries.pop_front(); + successFunc= &mail::addressbook::Add::added; + if (!addMessage->assemble()) + { + addMessage->fail(strerror(errno)); + return; + } + addMessage->go(); +} + + +mail::addressbook::Add::~Add() +{ +} + +void mail::addressbook::Add::success(string successMsg) +{ + (this->*successFunc)(successMsg); +} + +// +// After adding a new entry, make sure it gets added to the index. +// + +void mail::addressbook::Add::added(string successMsg) +{ + ++currentNum; + go(); +} + +// +// Now, update our address book index. + +void mail::addressbook::Add::checked(string successMsg) +{ + size_t n=addressBook->index.size(); + + size_t n2=addressBook->server->getFolderIndexSize(); + + vector<size_t> msgNums; + + while (n < n2) + msgNums.push_back(n++); + + if (msgNums.size() == 0) + { + reindexed(successMsg); // Unlikely + return; + } + + addressBook->index.insert(addressBook->index.end(), + msgNums.size(), Index()); + + successFunc= &mail::addressbook::Add::reindexed; + + addressBook->server->readMessageAttributes(msgNums, + addressBook->server + -> ENVELOPE, + *this); +} + +void mail::addressbook::Add::messageEnvelopeCallback(size_t messageNumber, + const mail::envelope + &envelope) +{ + addressBook->setIndex(messageNumber, envelope.subject); +} + +void mail::addressbook::Add::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + callback.reportProgress(bytesCompleted, bytesEstimatedTotal, + messagesCompleted, messagesEstimatedTotal); +} + +void mail::addressbook::Add::reindexed(string successMsg) +{ + try { + // If this is meant to replace another entry, delete it then. + + if (oldUid.size() > 0) + { + addressBook->del(oldUid, callback); + } + else + callback.success(successMsg); + delete this; + } catch (...) { + delete this; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +void mail::addressbook::Add::fail(string failMsg) +{ + try { + callback.fail(failMsg); + delete this; + } catch (...) { + delete this; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + diff --git a/libmail/addressbookadd.H b/libmail/addressbookadd.H new file mode 100644 index 0000000..51ff523 --- /dev/null +++ b/libmail/addressbookadd.H @@ -0,0 +1,65 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_addressbookadd_H +#define libmail_addressbookadd_H + +#include "addressbook.H" +#include "structure.H" +#include "addmessage.H" + +// +// Add new address book entry. + +class mail::addressbook::Add : public mail::callback::message { + + mail::ptr<mail::addressbook> addressBook; + + size_t totCnt; + size_t currentNum; + + std::list<mail::addressbook::Entry> newEntries; + std::string oldUid; + mail::callback &callback; + + mail::addMessage *addMessage; + size_t dummyRet; + + std::string nickname; + std::vector<size_t> multipart_params; + + void success(std::string successMsg); + void (mail::addressbook::Add::*successFunc)(std::string); + void addedIntro(std::string successMsg); + void addedBeef(std::string successMsg); + void addedAll(std::string successMsg); + void added(std::string successMsg); + void checked(std::string successMsg); + void reindexed(std::string successMsg); + + void fail(std::string failMsg); + + void messageEnvelopeCallback(size_t messageNumber, + const class mail::envelope &envelope); + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); +public: + Add(mail::addressbook *addressBookArg, + mail::addressbook::Entry entryArg, + std::string oldUidArg, + mail::callback &callbackArg); + + Add(mail::addressbook *addressBookArg, + std::list<mail::addressbook::Entry> &entries, + mail::callback &callbackArg); + ~Add(); + + void go(); +}; + +#endif diff --git a/libmail/addressbookget.C b/libmail/addressbookget.C new file mode 100644 index 0000000..32d6335 --- /dev/null +++ b/libmail/addressbookget.C @@ -0,0 +1,255 @@ +/* +** Copyright 2002-2011, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "misc.H" +#include "addressbookget.H" +#include "unicode/unicode.h" +#include <ctype.h> +#include <sstream> + +using namespace std; + +template<class T> +mail::addressbook::GetAddressList<T>::GetAddressList(mail::addressbook + *addressBookArg, + size_t msgNumArg, + std::vector<T> + &addrListRetArg, + mail::callback &callbackArg) + : addressBook(addressBookArg), + msgNum(msgNumArg), + addrListRet(addrListRetArg), + callback(callbackArg) +{ +} + +template<class T> +mail::addressbook::GetAddressList<T>::~GetAddressList() +{ +} + +// 1. Read the message's MIME structure. + +template<class T> +void mail::addressbook::GetAddressList<T>::go() +{ + successFunc= &mail::addressbook::GetAddressList<T>::readstructure; + + vector<size_t> msgNumVec; + + msgNumVec.push_back(msgNum); + + addressBook->server->readMessageAttributes(msgNumVec, + addressBook->server-> + MIMESTRUCTURE, + *this); +} + +template<class T> +void mail::addressbook::GetAddressList<T>::success(std::string successMsg) +{ + (this->*successFunc)(successMsg); +} + +// 2. Verify the presence of text/x-libmail-addressbook content. + +template<class T> +void mail::addressbook::GetAddressList<T>::readstructure(string successMsg) +{ + size_t n=mimeStructure.getNumChildren(); + mail::mimestruct *lastChild; + + if (n > 0 && (lastChild=mimeStructure.getChild(n-1)) + -> type == "TEXT" && + lastChild->subtype == "X-LIBMAIL-ADDRESSBOOK") + { + successFunc=&mail::addressbook::GetAddressList<T>::readContents; + addressBook->server->readMessageContentDecoded(msgNum, false, + *lastChild, + *this); + return; + } + + readContents(successMsg); +} + +template<class T> +void mail::addressbook::GetAddressList<T>::readContents(string successMsg) +{ + messageTextCallback(msgNum, "\n"); + + size_t errIndex; + + map<string, string>::iterator p; + + p=addressBookLineMap.find("VERSION"); + + int version=1; + + if (p != addressBookLineMap.end()) + { + istringstream i(p->second); + + i >> version; + } + + p=addressBookLineMap.find("ADDRESS"); + + if (p != addressBookLineMap.end()) + { + size_t n=addrListRet.size(); + + if (!mail::address::fromString(p->second, addrListRet, + errIndex)) + { + fail("Syntax error in address book entry"); + return; + } + + if (version < 2) // Raw UTF-8 + { + typename std::vector<T>::iterator + b=addrListRet.begin() + n, + e=addrListRet.end(); + + while (b != e) + { + mail::emailAddress + convAddress(mail::address("", + b->getAddr() + )); + + convAddress + .setDisplayName(b->getName(), "utf-8"); + *b=convAddress; + ++b; + } + } + } + + try { + callback.success("OK"); + delete this; + } catch (...) { + delete this; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +template<class T> +void mail::addressbook::GetAddressList<T>::fail(std::string failMsg) +{ + try { + callback.fail(failMsg); + delete this; + } catch (...) { + delete this; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +template<class T> +void mail::addressbook::GetAddressList<T> +::messageStructureCallback(size_t messageNumber, + const mail::mimestruct + &messageStructure) +{ + mimeStructure=messageStructure; +} + +// +// 3. Read text/x-libmail-addressbook content. +// + +template<class T> +void mail::addressbook::GetAddressList<T>::messageTextCallback(size_t n, + string text) +{ + size_t i; + + while ((i=text.find('\n')) != std::string::npos) + { + string line=linebuffer + text.substr(0, i); + + linebuffer=""; + text=text.substr(i+1); + addressBookLine(line); + } + + linebuffer=text; +} + +template<class T> +void mail::addressbook::GetAddressList<T> +::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + callback.reportProgress(bytesCompleted, bytesEstimatedTotal, + messagesCompleted, messagesEstimatedTotal); +} + +// Process a single x-libmail-addressbook line. + +template<class T> +void mail::addressbook::GetAddressList<T>::addressBookLine(string text) +{ + string::iterator b=text.begin(), e=text.end(); + string hdr; + + if (b == e) + return; + + if ( unicode_isspace((unsigned char)*b)) + { + while (b != e && + unicode_isspace((unsigned char)*b)) + b++; + + text= " " + string(b, e); + + hdr=lastAddressBookLine; + } + else + { + size_t i=text.find(':'); + + if (i == std::string::npos) + return; + + hdr=text.substr(0, i); + + text=text.substr(i+1); + + while (text.size() > 0 && + unicode_isspace((unsigned char)text[0])) + text=text.substr(1); + mail::upper(hdr); + lastAddressBookLine=hdr; + } + + map<string, string>::iterator p=addressBookLineMap.find(hdr); + + if (p != addressBookLineMap.end()) + { + text= p->second + " " + text; + addressBookLineMap.erase(p); + } + + // Sanity check + + if (text.size() > 8000) + text.erase(text.begin()+8000, text.end()); + + if (addressBookLineMap.size() > 100) + return; + + addressBookLineMap.insert(make_pair(hdr, text)); +} + +template class mail::addressbook::GetAddressList<mail::address>; +template class mail::addressbook::GetAddressList<mail::emailAddress>; diff --git a/libmail/addressbookget.H b/libmail/addressbookget.H new file mode 100644 index 0000000..90ff1bc --- /dev/null +++ b/libmail/addressbookget.H @@ -0,0 +1,69 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_addressbookget_H +#define libmail_addressbookget_H + +#include "addressbook.H" +#include "structure.H" +#include "rfcaddr.H" + +#include <map> + +// +// Extract an address book entry, as follows: +// +// 1. Read the message's MIME structure. +// 2. Verify the presence of text/x-libmail-addressbook content. +// 3. Read text/x-libmail-addressbook content. + +template<class T> +class mail::addressbook::GetAddressList : public mail::callback::message { + + mail::ptr<mail::addressbook> addressBook; + size_t msgNum; + std::vector<T> &addrListRet; + mail::callback &callback; + mail::mimestruct mimeStructure; + + void success(std::string successMsg); + void (mail::addressbook::GetAddressList<T>::*successFunc)(std::string); + + void readstructure(std::string successMsg); + void readContents(std::string successMsg); + + void fail(std::string failMsg); + + void messageStructureCallback(size_t messageNumber, + const mail::mimestruct + &messageStructure); + void messageTextCallback(size_t n, std::string text); + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + + std::string linebuffer; + // Reading x-libmail-addressbook content; partial line read. + + void addressBookLine(std::string text); + + std::map<std::string, std::string> addressBookLineMap; + std::string lastAddressBookLine; + // Name of the current x-libmail-addressbook header line being read. + +public: + GetAddressList(mail::addressbook *addressBookArg, + size_t msgNumArg, + std::vector<T> &addrListRetArg, + mail::callback &callbackArg); + ~GetAddressList(); + + void go(); +}; + +#endif diff --git a/libmail/addressbookopen.C b/libmail/addressbookopen.C new file mode 100644 index 0000000..fd240c2 --- /dev/null +++ b/libmail/addressbookopen.C @@ -0,0 +1,98 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "addressbookopen.H" +#include "envelope.H" + +using namespace std; + +mail::addressbook::Open::Open(mail::addressbook *addressBookArg, + mail::callback &callbackArg) + : addressBook(addressBookArg), + callback(callbackArg) +{ +} + +mail::addressbook::Open::~Open() +{ +} + +void mail::addressbook::Open::success(string msg) +{ + (this->*successFunc)(msg); +} + +void mail::addressbook::Open::fail(string msg) +{ + try { + callback.fail(msg); + delete this; + } catch (...) { + delete this; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +// 1. Select the folder + +void mail::addressbook::Open::go() +{ + successFunc= &mail::addressbook::Open::opened; + + addressBook->folder->open(*this, NULL, *addressBook); +} + +// 2. Read envelopes of all messages in the folder + +void mail::addressbook::Open::opened(string successMsg) +{ + addressBook->index.clear(); + addressBook->index.insert(addressBook->index.end(), + addressBook->server->getFolderIndexSize(), + Index()); + + vector<size_t> msgs; + + size_t i; + + for (i=0; i<addressBook->index.size(); i++) + msgs.push_back(i); + + successFunc= &mail::addressbook::Open::readIndex; + + addressBook->server->readMessageAttributes(msgs, + addressBook->server + -> ENVELOPE, + *this); +} + +void mail::addressbook::Open::readIndex(string msg) +{ + try { + callback.success(msg); + delete this; + } catch (...) { + delete this; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +void mail::addressbook::Open +::messageEnvelopeCallback(size_t messageNumber, + const class mail::envelope &envelope) +{ + addressBook->setIndex(messageNumber, envelope.subject); +} + +void mail::addressbook::Open::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + callback.reportProgress(bytesCompleted, bytesEstimatedTotal, + messagesCompleted, messagesEstimatedTotal); +} diff --git a/libmail/addressbookopen.H b/libmail/addressbookopen.H new file mode 100644 index 0000000..b00995c --- /dev/null +++ b/libmail/addressbookopen.H @@ -0,0 +1,49 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_addressbookopen_H +#define libmail_addressbookopen_H + +#include "addressbook.H" +#include "structure.H" + +// +// Open the address book, as follows: +// +// 1. Select the folder. +// +// 2. Read envelopes of all messages in the folder + +class mail::addressbook::Open : public mail::callback::message { + + mail::ptr<mail::addressbook> addressBook; + mail::callback &callback; + + void success(std::string successMsg); + void (mail::addressbook::Open::*successFunc)(std::string); + + void fail(std::string failMsg); + + void opened(std::string successMsg); + void readIndex(std::string successMsg); + + void messageEnvelopeCallback(size_t messageNumber, + const class mail::envelope &envelope); + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + +public: + Open(mail::addressbook *addressBookArg, + mail::callback &callbackArg); + ~Open(); + + void go(); +}; + +#endif diff --git a/libmail/addressbooksearch.C b/libmail/addressbooksearch.C new file mode 100644 index 0000000..50f1984 --- /dev/null +++ b/libmail/addressbooksearch.C @@ -0,0 +1,82 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "addressbooksearch.H" + +using namespace std; + +template<class T> +mail::addressbook::Search<T>::Search(mail::addressbook *addressBookArg, + vector<T> &addrListArg, + mail::callback &callbackArg) + : addressBook(addressBookArg), + addrList(addrListArg), + callback(callbackArg) +{ +} + +template<class T> +mail::addressbook::Search<T>::~Search() +{ +} + +template<class T> +void mail::addressbook::Search<T>::success(std::string msg) +{ + go(); +} + +template<class T> +void mail::addressbook::Search<T>::fail(std::string msg) +{ + try { + callback.fail(msg); + delete this; + } catch (...) { + delete this; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +template<class T> +void mail::addressbook::Search<T>::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + callback.reportProgress(bytesCompleted, bytesEstimatedTotal, + messagesCompleted, messagesEstimatedTotal); +} + +template<class T> +void mail::addressbook::Search<T>::go() +{ + list<string>::iterator b=uidList.begin(), e=uidList.end(); + + if (b == e) // Finished the list. + { + try { + callback.success("OK"); + delete this; + } catch (...) { + delete this; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + return; + } + + // Just get the next address book entry. + + string uid= *b; + + uidList.erase(b); + + addressBook->getEntry( uid, addrList, *this ); +} + +template class mail::addressbook::Search<mail::address>; +template class mail::addressbook::Search<mail::emailAddress>; diff --git a/libmail/addressbooksearch.H b/libmail/addressbooksearch.H new file mode 100644 index 0000000..36a516b --- /dev/null +++ b/libmail/addressbooksearch.H @@ -0,0 +1,48 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_addressbooksearch_H +#define libmail_addressbooksearch_H + +#include "libmail_config.h" +#include "addressbook.H" + +#include "rfcaddr.H" + +// +// Search the address book. The main mail::addressbook object already compiled +// a list of address book entries, our job is to just to read them and +// combine them. +// + +template<class T> +class mail::addressbook::Search : private mail::callback { + + mail::ptr<mail::addressbook> addressBook; + std::vector<T> &addrList; + mail::callback &callback; + + void success(std::string); + void fail(std::string); + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + +public: + Search(mail::addressbook *addressBookArg, + std::vector<T> &addrListArg, + mail::callback &callbackArg); + ~Search(); + + std::list< std::string > uidList; + + void go(); + +}; + +#endif diff --git a/libmail/attachments.C b/libmail/attachments.C new file mode 100644 index 0000000..f9c9a5a --- /dev/null +++ b/libmail/attachments.C @@ -0,0 +1,631 @@ +/* +** Copyright 2004-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "misc.H" +#include "headers.H" +#include "rfc822/encode.h" +#include "rfc2045/rfc2045charset.h" +#include <sys/time.h> +#include "attachments.H" +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <sstream> +#include <iomanip> +#include <cstring> + +using namespace std; + +void mail::Attachment::common_init() +{ + content_fp=NULL; + message_rfc822=NULL; + generating=false; +} + +void mail::Attachment::common_fd_init(int fd) +{ + int n; + FILE *fp; + + if (lseek(fd, 0L, SEEK_END) >= 0 && + lseek(fd, 0L, SEEK_SET) >= 0) + { + int fd2=dup(fd); + + if (fd2 < 0) + throw strerror(errno); + + if ((content_fp=fdopen(fd2, "r")) == NULL) + { + close(fd2); + throw strerror(errno); + } + return; + } + + fp=tmpfile(); + if (!fp) + { + throw strerror(errno); + } + + while ((n=read(fd, encode_info.output_buffer, + // Convenient buffer + sizeof(encode_info.output_buffer))) > 0) + { + if (fwrite(encode_info.output_buffer, n, 1, fp) != 1) + { + fclose(fp); + throw strerror(errno); + } + } + + if (fflush(fp) < 0 || ferror(fp)) + { + fclose(fp); + throw strerror(errno); + } + content_fp=fp; +} + +void mail::Attachment::check_multipart_encoding() +{ + string::iterator cte_start, cte_end; + + multipart_type=find_header("CONTENT-TYPE:", cte_start, cte_end); + mail::upper(multipart_type); + + const char *p=multipart_type.c_str(); + + if (strncmp(p, "MULTIPART/", 10) == 0 || + strcmp(p, "MESSAGE/RFC822") == 0) + transfer_encoding="8bit"; +} + +mail::Attachment::Attachment(string h, int fd) + : headers(h) +{ + common_init(); + common_fd_init(fd); + + try { + check_multipart_encoding(); + + if (transfer_encoding.size() == 0) + transfer_encoding= + libmail_encode_autodetect_fp(content_fp, 1, + NULL); + } catch (...) + { + fclose(content_fp); + content_fp=NULL; + } + + add_content_encoding(); +} + +mail::Attachment::~Attachment() +{ + if (content_fp) + fclose(content_fp); + if (message_rfc822 == NULL && + parts.size() == 0) + { + if (generating) + libmail_encode_end(&encode_info); + } +} + +mail::Attachment::Attachment(std::string h, int fd, + std::string chs, + std::string encoding) + + : headers(h) +{ + common_init(); + common_fd_init(fd); + + try { + transfer_encoding=encoding; + + if (transfer_encoding.size() == 0) + check_multipart_encoding(); + + if (transfer_encoding.size() == 0) + transfer_encoding= + libmail_encode_autodetect_fp(content_fp, 0, + NULL); + add_content_encoding(); + + } catch (...) { + fclose(content_fp); + throw; + } +} + +mail::Attachment::Attachment(string h, string content) + : headers(h), content_string(content) +{ + common_init(); + check_multipart_encoding(); + if (transfer_encoding.size() == 0) + transfer_encoding= + libmail_encode_autodetect_buf(content.c_str(), 1); + + add_content_encoding(); +} + +mail::Attachment::Attachment(string h, string content, + string chs, + string encoding) + : headers(h), content_string(content) +{ + common_init(); + transfer_encoding=encoding; + + if (transfer_encoding.size() == 0) + check_multipart_encoding(); + + if (transfer_encoding.size() == 0) + transfer_encoding= + libmail_encode_autodetect_buf(content.c_str(), 1); + add_content_encoding(); +} + +void mail::Attachment::add_content_encoding() +{ + string::iterator cte_start, cte_end; + + multipart_type=find_header("CONTENT-TYPE:", cte_start, cte_end); + mail::upper(multipart_type); + + string existing_transfer_encoding= + find_header("CONTENT-TRANSFER-ENCODING:", cte_start, + cte_end); + + const char *p=multipart_type.c_str(); + + if (cte_start != cte_end || + // Already have the header. Must be already encoded. + + strncmp(p, "MULTIPART/", 10) == 0 || + strcmp(p, "MESSAGE/RFC822") == 0) + { + transfer_encoding="8bit"; + return; + } + + headers += "Content-Transfer-Encoding: "; + headers += transfer_encoding; + headers += "\n"; +} + +mail::Attachment::Attachment(string h, + const vector<Attachment *> &partsArg) + : headers(h) +{ + common_init(); + parts=partsArg; + common_multipart_init(); +} + +mail::Attachment::Attachment(string h, + const vector<Attachment *> &partsArg, + string multipart_typeArg, + const mail::mimestruct::parameterList + &multipart_parametersArg) + : headers(h) +{ + common_init(); + parts=partsArg; + multipart_type=multipart_typeArg; + multipart_parameters=multipart_parametersArg; + common_multipart_init(); +} + +string mail::Attachment::find_header(const char *header_name, + string::iterator &hstart, + string::iterator &hend) +{ + return find_header(header_name, headers, hstart, hend); +} + +std::string mail::Attachment::find_header(const char *header_name, + std::string &headers, + std::string::iterator &hstart, + std::string::iterator &hend) +{ + size_t l=strlen(header_name); + + string::iterator b=headers.begin(), e=headers.end(); + string::iterator s=b; + + while (b != e) + { + while (b != e) + { + if (*b == '\n') + { + ++b; + break; + } + ++b; + } + + string h=string(s, b); + + mail::upper(h); + + if (strncmp(h.c_str(), header_name, l) == 0) + break; + s=b; + } + + while (b != e) + { + if (*b != ' ' && *b != '\t') + break; + + while (b != e) + { + if (*b == '\n') + { + ++b; + break; + } + ++b; + } + } + + hstart=s; + hend=b; + + if (s != b) + s += l; + + while (s != b && unicode_isspace((unsigned char)*s)) + ++s; + + while (s != b && unicode_isspace((unsigned char)b[-1])) + --b; + + return string(s,b); +} + +void mail::Attachment::common_multipart_init() +{ + string::iterator s, b; + + string content_type=find_header("CONTENT-TYPE:", s, b); + + string header=string(s,b); + + if (s != b) + headers.erase(s,b); + + mail::Header::mime mimeHeader= mail::Header::mime::fromString(header); + + if (multipart_type.size() == 0) + multipart_type=mimeHeader.value; + multipart_parameters=mimeHeader.parameters; + + if (parts.size() == 0) // Woot? + { + multipart_type="text/plain"; + transfer_encoding="8bit"; + return; + } + + mail::upper(multipart_type); + + if (multipart_type == "MESSAGE/RFC822") + { + if (parts.size() > 1) // Woot? + { + multipart_type="MULTIPART/MIXED"; + return; + } + message_rfc822=parts[0]; + } + else + { + if (strncmp(multipart_type.c_str(), "MESSAGE/", 8) == 0) + multipart_type="MULTIPART/MIXED"; + } + +} + +void mail::Attachment::begin() +{ + begin_recursive(); + + string::iterator s, b; + + string content_type=find_header("MIME-VERSION:", s, b); + + if (s == b) + headers="Mime-Version: 1.0\n" + headers; + + string boundary; + + do + { + ostringstream fmt; + + struct timeval tv; + + gettimeofday(&tv, NULL); + + fmt << "=_" << mail::hostname() << "-" + << tv.tv_sec + << "-" + << tv.tv_usec + << "-" + << getpid(); + + + boundary=fmt.str(); + } while (!try_boundary(boundary, 0)); +} + +bool mail::Attachment::try_boundary(std::string templ, int level) +{ + string boundary; + ostringstream fmt; + + // =_(mumble) can never occur in base64 or quoted-printable + + if (transfer_encoding == "base64") + return true; + if (transfer_encoding == "quoted-printable") + return true; + + fmt << templ << "-" << setw(4) << setfill('0') << level; + boundary=fmt.str(); + + if (content_string.size() > 0) + { + const char *p=content_string.c_str(); + + while (*p) + { + if (strncmp(p, "--", 2) == 0 && + strncasecmp(p+2, boundary.c_str(), + boundary.size()) == 0) + return false; + + while (*p && *p != '\n') + ++p; + if (*p) + ++p; + } + } + if (content_fp) + { + if (fseek(content_fp, 0L, SEEK_SET) < 0) + throw strerror(errno); + + while (fgets(encode_info.output_buffer, + // Convenient buffer + + sizeof(encode_info.output_buffer), content_fp)) + { + if (strncmp(encode_info.output_buffer, "--", 2) == 0 && + strncasecmp(encode_info.output_buffer+2, + boundary.c_str(), + boundary.size()) == 0) + return false; + } + if (ferror(content_fp)) + throw strerror(errno); + } + + if (message_rfc822) + return message_rfc822->try_boundary(templ, level); + + std::vector<mail::Attachment *>::iterator b, e; + + for (b=parts.begin(), e=parts.end(); b != e; ++b) + if (!(*b)->try_boundary(templ, level+1)) + return false; + + if (parts.size() > 0) + { + multipart_parameters.erase("boundary"); + multipart_parameters.set_simple("boundary", boundary); + } + + return true; +} + +void mail::Attachment::begin_recursive() +{ + std::vector<mail::Attachment *>::iterator b, e; + + generating=false; + + for (b=parts.begin(), e=parts.end(); b != e; ++b) + (*b)->begin_recursive(); +} + +string mail::Attachment::generate(bool &error) +{ + if (message_rfc822) + { + if (!generating) + { + generating=true; + mail::Header::mime content_type("Content-Type", + multipart_type); + + content_type.parameters=multipart_parameters; + + return headers + content_type.toString() + "\n\n"; + } + return message_rfc822->generate(error); + } + + if (parts.size() > 0) + { + if (!generating) + { + generating=true; + multipart_iterator=parts.begin(); + + mail::Header::mime content_type("Content-Type", + multipart_type); + + content_type.parameters=multipart_parameters; + + return headers + + content_type.toString() + "\n\n" + RFC2045MIMEMSG "\n--" + + multipart_parameters.get("boundary") + + "\n"; + } + + if (multipart_iterator == parts.end()) + return ""; + + string s=(*multipart_iterator)->generate(error); + + if (error) + return s; + + if (s.size() > 0) + return s; + + ++multipart_iterator; + + return "\n--" + multipart_parameters.get("boundary") + + (multipart_iterator == parts.end() + ? "--\n":"\n"); + } + + if (!generating) + { + generating=true; + if (content_fp) + { + if (fseek(content_fp, 0L, SEEK_SET) < 0) + { + error=true; + return ""; + } + } + + libmail_encode_start(&encode_info, + transfer_encoding.c_str(), + &mail::Attachment::callback_func, + this); + + encoded=headers + "\n"; + if (content_fp) + return encoded; + if (libmail_encode(&encode_info, + content_string.c_str(), + content_string.size()) < 0) + { + error=true; + return ""; + } + libmail_encode_end(&encode_info); + return encoded; + } + + encoded=""; + if (content_fp) + { + if (feof(content_fp)) + { + libmail_encode_end(&encode_info); + return encoded; + } + + do + { + char buf[BUFSIZ]; + int n=fread(buf, 1, sizeof(buf), content_fp); + + if (n < 0) + { + error=true; + return ""; + } + + if (n == 0) + { + libmail_encode_end(&encode_info); + return encoded; + } + + if (libmail_encode(&encode_info, buf, n) < 0) + { + error=true; + return ""; + } + } while (encoded.size() == 0); + // libmail_encode() may not call the callback function, + // so encoded will be empty, which will be interpreted by + // the caller as EOF, so we simply try again. + } + return encoded; +} + +int mail::Attachment::callback_func(const char *c, size_t n, void *va) +{ + ((mail::Attachment *)va)->encoded += string(c, c+n); + return 0; +} + +mail::Attachment::Attachment(const mail::Attachment &a) +{ + common_init(); + + (*this)=a; +} + +#define CPY(n) n=o.n + +mail::Attachment &mail::Attachment::operator=(const Attachment &o) +{ + CPY(headers); + CPY(transfer_encoding); + if (content_fp) + fclose(content_fp); + + content_fp=NULL; + + if (o.content_fp) + { + int fd2=dup(fileno(o.content_fp)); + + if (fd2 < 0) + throw strerror(errno); + + if ((content_fp=fdopen(fd2, "r")) == NULL) + { + close(fd2); + throw strerror(errno); + } + } + CPY(content_string); + CPY(parts); + + message_rfc822=NULL; + if (o.message_rfc822) + message_rfc822=parts[0]; + + CPY(multipart_type); + CPY(multipart_parameters); + CPY(generating); + CPY(multipart_iterator); + CPY(encode_info); + CPY(encoded); + return *this; +} diff --git a/libmail/attachments.H b/libmail/attachments.H new file mode 100644 index 0000000..33f11f1 --- /dev/null +++ b/libmail/attachments.H @@ -0,0 +1,147 @@ +/* +** Copyright 2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_attachments_H +#define libmail_attachments_H + +/////////////////////////////////////////////////////////////////////////// +// +// mail::Attachment is a versatile object that's used to build arbitrary +// MIME objects. +// +// A mail::Attachment object can represent a: +// +// 1) A content-containing MIME entity. The mail::Attachment object +// can optionally select the most appropriate encoding method. +// +// 2) A multipart MIME entity. +// +// 3) A message/rfc822 entity. + + +#include <string> +#include <vector> +#include <stdio.h> +#include "namespace.H" +#include "structure.H" +#include "rfc822/encode.h" + +LIBMAIL_START + +class Attachment { + + std::string headers; // All entities: the headers + + std::string transfer_encoding; + // If not empty - this is a content entity + + FILE *content_fp; // File descriptor with the content, or + std::string content_string; // The content in memory + + mail::Attachment *message_rfc822; // This is a message/rfc822 object + + std::vector<mail::Attachment *> parts; // This is a multipart object + + std::string multipart_type; + mail::mimestruct::parameterList multipart_parameters; + + bool try_boundary(std::string, int level); + + void common_init(); + void common_fd_init(int content_fd); +public: + + // + // The headers parameter in the following constructions should ideally + // be created using mail::Header::list. In all cases the string MUST + // have a trailing newline. + // + + Attachment(std::string headers, int content_fd); + // Content attachment, autodetect encoding + + Attachment(std::string headers, int content_fd, + std::string charset, + std::string transfer_encoding=""); + // Content attachment uses the given encoding. transfer_encoding may + // be an empty string, in which case the encoding is picked based on + // charset. + + // The following two constructors receive the literal content, + // instead of a file descriptor. + Attachment(std::string headers, std::string content_string); + Attachment(std::string headers, std::string content_string, + std::string charset, + std::string transfer_encoding=""); + + // + // Create a multipart attachment. The Content-Type: header is + // extracted from 'headers' and parsed. + // + // If Content-Type: is message/rfc822, the parts vector's length must + // be exactly 1. + + Attachment(std::string headers, + const std::vector<Attachment *> &parts); + Attachment(std::string headers, + const std::vector<Attachment *> &parts, + std::string multipart_type, + const mail::mimestruct::parameterList + &multipart_parameters); +private: + void check_multipart_encoding(); + + void common_multipart_init(); +public: + + ~Attachment(); + + + // Generating the MIME object. + + void begin(); + std::string generate(bool &error); + + // Return the next chunk of the generated object. + // Returns an empty string when done, or if an error occured. + // (If error=false, we're done) + +private: + void begin_recursive(); + + bool generating; + std::vector<mail::Attachment *>::iterator multipart_iterator; + + struct libmail_encode_info encode_info; + + static int callback_func(const char *, size_t, void *); + + std::string encoded; + +public: + + // NOTE: the copy constructor and assignment operator does NOT + // duplicate the subtree. + + Attachment(const Attachment &); + Attachment &operator=(const Attachment &); + + +private: + std::string find_header(const char *, + std::string::iterator &, + std::string::iterator &); + void add_content_encoding(); +public: + static std::string find_header(const char *, + std::string &, + std::string::iterator &, + std::string::iterator &); +}; + + +LIBMAIL_END + +#endif diff --git a/libmail/autodecoder.C b/libmail/autodecoder.C new file mode 100644 index 0000000..8065fd9 --- /dev/null +++ b/libmail/autodecoder.C @@ -0,0 +1,67 @@ +/* +** Copyright 2002-2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "misc.H" +#include "autodecoder.H" + +using namespace std; + +mail::autodecoder::base64::base64(mail::autodecoder &meArg) + : me(meArg) +{ +} + +mail::autodecoder::base64::~base64() +{ +} + +void mail::autodecoder::base64::decoded(string s) +{ + me.decoded(s); +} + +mail::autodecoder::qp::qp(mail::autodecoder &meArg) + : me(meArg) +{ +} + +mail::autodecoder::qp::~qp() +{ +} + +void mail::autodecoder::qp::decoded(string s) +{ + me.decoded(s); +} + +////////////////////////////////////////////////////////////////////// + +mail::autodecoder::autodecoder(string cte) + : base64Decoder(*this), + qpDecoder(*this), + decoder(NULL) +{ + mail::upper(cte); + + if (cte == "QUOTED-PRINTABLE") + decoder= &qpDecoder; + + if (cte == "BASE64") + decoder= &base64Decoder; +} + +mail::autodecoder::~autodecoder() +{ +} + +void mail::autodecoder::decode(string s) +{ + if (decoder) + decoder->decode(s); // 7bit or 8bit, or something... + else + decoded(s); +} + diff --git a/libmail/autodecoder.H b/libmail/autodecoder.H new file mode 100644 index 0000000..d8d59ec --- /dev/null +++ b/libmail/autodecoder.H @@ -0,0 +1,62 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_autodecoder_H +#define libmail_autodecoder_H + +#include "decoder.H" +#include "base64.H" +#include "qp.H" +#include "namespace.H" + +// +// Generic MIME decoder. A mail::decoder subclass that uses either base64 +// or qp, where appropriate. + +LIBMAIL_START + +class autodecoder : public decoder { + + // Declare both base64 and qp decoding members. + + class base64 : public decodebase64 { + + autodecoder &me; + + public: + base64(autodecoder &meArg); + ~base64(); + private: + void decoded(std::string); + }; + + class qp : public decodeqp { + autodecoder &me; + + public: + qp(autodecoder &meArg); + ~qp(); + private: + void decoded(std::string); + }; + + base64 base64Decoder; + qp qpDecoder; + + decoder *decoder; // Points to one of these two, or NULL + +public: + autodecoder(std::string contentTransferEncoding); + ~autodecoder(); + + void decode(std::string s); + + virtual void decoded(std::string)=0; + +}; + +LIBMAIL_END + +#endif diff --git a/libmail/autodeps.C b/libmail/autodeps.C new file mode 100644 index 0000000..cb9ffe2 --- /dev/null +++ b/libmail/autodeps.C @@ -0,0 +1,26 @@ +/* +** Copyright 2003-2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ + +#include "libmail_config.h" +#include "addmessage.H" +#include "addressbook.H" +#include "attachments.H" +#include "autodecoder.H" +#include "base64.H" +#include "envelope.H" +#include "headers.H" +#include "logininfo.H" +#include "mail.H" +#include "mimetypes.H" +#include "qp.H" +#include "rfc2047decode.H" +#include "rfc2047encode.H" +#include "rfcaddr.H" +#include "smtpinfo.H" +#include "structure.H" +#include "snapshot.H" +#include "sync.H" + diff --git a/libmail/base64.C b/libmail/base64.C new file mode 100644 index 0000000..6b2d19a --- /dev/null +++ b/libmail/base64.C @@ -0,0 +1,198 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "base64.H" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +using namespace std; + +// base64 decoding table. + +static const unsigned char decode64tab[256]={ + 100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100, + 100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100, + 100,100,100,100,100,100,100,100,100,100,100,62,100,100,100,63, + 52,53,54,55,56,57,58,59,60,61,100,100,100,99,100,100, + 100,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14, + 15,16,17,18,19,20,21,22,23,24,25,100,100,100,100,100, + 100,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, + 41,42,43,44,45,46,47,48,49,50,51,100,100,100,100,100, + 100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100, + 100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100, + 100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100, + 100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100, + 100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100, + 100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100, + 100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100, + 100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100}; + + +mail::decodebase64::decodebase64() +{ + decodeBuffer=""; +} + +mail::decodebase64::~decodebase64() +{ +} + +void mail::decodebase64::decode(string text) +{ + char workbuf[BUFSIZ]; + + // Toss out the crud (newlines, spaces, etc..) + + string::iterator b=text.begin(), e=text.end(); + string::iterator c=b; + + while (b != e) + { + if (decode64tab[(int)(unsigned char)*b] == 100) + { + // Ignore non-base64 characters. + b++; + continue; + } + *c++=*b++; + } + + // Something might be left over from the previous run. Tis ok. + + b=text.begin(); + + if (c != b) + decodeBuffer.append(b, c); + + + size_t i=decodeBuffer.length()/4; // The remainder gets left over. + + char aa,bb,cc; + + b=decodeBuffer.begin(); + + size_t k=0; + + string decodedbuf=""; + + while (i) + { + unsigned char d=*b++; + unsigned char e=*b++; + unsigned char f=*b++; + unsigned char g=*b++; + + int w=decode64tab[d]; + int x=decode64tab[e]; + int y=decode64tab[f]; + int z=decode64tab[g]; + + aa= (w << 2) | (x >> 4); + bb= (x << 4) | (y >> 2); + cc= (y << 6) | z; + + workbuf[k++]=aa; + if ( f != '=') + workbuf[k++]=bb; + if ( g != '=') + workbuf[k++]=cc; + + if (k >= sizeof(workbuf)-10) + { + decodedbuf.append(workbuf, k); + k=0; + } + --i; + } + if (k > 0) + decodedbuf.append(workbuf, k); + + string left=""; + left.append(b, decodeBuffer.end()); + decodeBuffer=left; + + decoded(decodedbuf); +} + +// +// The encoder does NOT insert newline breaks. That's the superclass's job. + +mail::encodebase64::encodebase64() +{ + libmail_encode_start(&encodeInfo, "base64", &callback_func, this); +} + +mail::encodebase64::~encodebase64() +{ +} + +void mail::encodebase64::encode(string text) +{ + libmail_encode(&encodeInfo, &text[0], text.size()); +} + +void mail::encodebase64::flush() +{ + libmail_encode_end(&encodeInfo); +} + +int mail::encodebase64::callback_func(const char *ptr, size_t len, void *vp) +{ + string s=string(ptr, ptr+len); + size_t n; + + while ((n=s.find('\n')) != std::string::npos) + s.erase(s.begin()+n); + + ((mail::encodebase64 *)vp)->encoded(s); + return 0; +} + +///////////////////////////////////////////////////////////////////////////// +// +// The convenience classes. + +mail::decodebase64str::decodebase64str() + : challengeStr("") +{ +} + +mail::decodebase64str::decodebase64str(string s) + : challengeStr("") +{ + decode(s); +} + +mail::decodebase64str::~decodebase64str() +{ +} + +void mail::decodebase64str::decoded(string s) +{ + challengeStr += s; +} + +mail::encodebase64str::encodebase64str() : responseStr("") +{ +} + +mail::encodebase64str::encodebase64str(string s) + : responseStr("") +{ + encode(s); + flush(); +} + +mail::encodebase64str::~encodebase64str() +{ +} + +void mail::encodebase64str::encoded(string s) +{ + responseStr += s; +} + diff --git a/libmail/base64.H b/libmail/base64.H new file mode 100644 index 0000000..102ec23 --- /dev/null +++ b/libmail/base64.H @@ -0,0 +1,91 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_base64_h +#define libmail_base64_h + +#include "libmail_config.h" +#include "decoder.H" + +#include <string> + +#include "rfc822/encode.h" + +LIBMAIL_START + +// +// MIME base64 decoder. +// + +class decodebase64 : public decoder { + + std::string decodeBuffer; + +public: + decodebase64(); + virtual ~decodebase64(); + void decode(std::string text); // text - base64 text + +private: + virtual void decoded(std::string buffer)=0; + // decoded contents +}; + +// MIME base64 encoder + +class encodebase64 { + + struct libmail_encode_info encodeInfo; + + static int callback_func(const char *, size_t, void *); + +public: + encodebase64(); + virtual ~encodebase64(); + + void encode(std::string text); // text - binary data to encode + void flush(); // Flush any buffered encoded data. +private: + virtual void encoded(std::string buffer)=0; +}; + +// +// A helper object that collects the output of mail::decodebase64 into a +// single string +// + +class decodebase64str : public decodebase64 { + +public: + std::string challengeStr; + decodebase64str(); + decodebase64str(std::string); + ~decodebase64str(); + void decoded(std::string s); + + operator std::string() const { return (challengeStr); } +}; + +// +// A helper object that base64-encodes a single chunk of data, and returns it. +// + +class encodebase64str : public encodebase64 { + +public: + std::string responseStr; + + encodebase64str(); + encodebase64str(std::string); + ~encodebase64str(); + + void encoded(std::string s); + + operator std::string() const { return (responseStr); } +}; + +LIBMAIL_END + +#endif diff --git a/libmail/configure.in b/libmail/configure.in new file mode 100644 index 0000000..16a4c5e --- /dev/null +++ b/libmail/configure.in @@ -0,0 +1,132 @@ +# +# Copyright 2002-2009, Double Precision Inc. +# +# See COPYING for distribution information. +# + +dnl Process this file with autoconf to produce a configure script. +AC_INIT(libmail, 0.10, [courier-cone@lists.sourceforge.net]) +>confdefs.h # Kill PACKAGE_MACROS + +AC_CONFIG_SRCDIR(mail.C) +AC_CONFIG_AUX_DIR(../..) +AM_CONFIG_HEADER(libmail_config.h) +AM_INIT_AUTOMAKE([no-define]) + +dnl Checks for programs. +AC_PROG_CXX +AC_PROG_AWK +AC_PROG_CC +AC_PROG_INSTALL +AC_PROG_LN_S +AM_PROG_LIBTOOL + +LPATH="$PATH:/usr/local/bin:/usr/sbin:/sbin:/lib" + +AC_PATH_PROG(SENDMAIL, sendmail, /usr/bin/sendmail, $LPATH) + +AC_DEFINE_UNQUOTED(SENDMAIL, "$SENDMAIL", + [ Local sendmail program ]) + +dnl Checks for libraries. + +AC_ARG_WITH(libidn, AC_HELP_STRING([--with-libidn=[DIR]], + [Support IDN (needs GNU Libidn)]), + libidn=$withval, libidn=yes) + +if test "$libidn" != "no" +then + PKG_CHECK_MODULES(LIBIDN, libidn >= 0.0.0, [libidn=yes], [libidn=no]) + if test "$libidn" != "yes" + then + libidn=no + AC_MSG_WARN([Libidn not found]) + else + libidn=yes + AC_DEFINE(LIBIDN, 1, [Define to 1 if you want Libidn.]) + fi +fi +AC_MSG_CHECKING([if Libidn should be used]) +AC_MSG_RESULT($libidn) + +dnl Checks for header files. +AC_CHECK_HEADERS(sys/time.h sys/wait.h unistd.h sys/select.h fcntl.h utime.h termios.h) +AC_HEADER_TIME +AC_HEADER_DIRENT +AC_HEADER_SYS_WAIT + +dnl Checks for typedefs, structures, and compiler characteristics. + +AC_TYPE_SIGNAL +AC_SYS_LARGEFILE + +dnl Checks for library functions. + +if test "$GXX" = "yes" +then + CPPFLAGS="-Wall $CPPFLAGS" +fi + +. ../tcpd/couriertls.config + +if test "$couriertls" != "" +then + LIBCOURIERTLS="../tcpd/libcouriertls.la" +fi +AC_SUBST(LIBCOURIERTLS) + +CPPFLAGS="-I.. -I${srcdir}/.. $cppflags $CPPFLAGS" + +changequote(<,>) + +echo 'static const char * const mimetypefiles[]={' >mimetypefiles.h + +changequote([,]) + +AC_ARG_ENABLE(mimetypes, [ --enable-mimetypes={dir} Your mime.types file.], + echo "\"$enableval\"," >>mimetypefiles.h +) + +for f in /usr/lib /usr/local/lib /usr/lib/pine /usr/local/lib/pine /etc \ + /var/lib/httpd/conf /home/httpd/conf /usr/local/etc/apache \ + /usr/local/apache/conf /var/lib/apache/etc +do + if test -f $f/mime.types + then + echo "\"$f/mime.types\"," >>mimetypefiles.h + fi +done + +echo '0};' >>mimetypefiles.h + +AC_ARG_WITH(devel, [ --with-devel Install development libraries], + devel="$withval", devel=no) + +case "$devel" in +y*|Y*) + INSTINCLUDES="install-includes" + UNINSTINCLUDES="uninstall-includes" + ;; +esac +AC_SUBST(INSTINCLUDES) +AC_SUBST(UNINSTINCLUDES) + +AC_CHECK_FUNCS(utime utimes) + +# Debugging: + +AC_ARG_ENABLE(debugging, [ --enable-debugging Maintainer option ], + debugging="$enableval", debugging="no") + +case "$debugging" in +y*|Y*) + debugging="1" + ;; +*) + debugging="0" + ;; +esac + +AC_DEFINE_UNQUOTED(LIBMAIL_THROW_DEBUG, $debugging, [ Debugging purposes ]) + +AC_OUTPUT(Makefile) diff --git a/libmail/copymessage.C b/libmail/copymessage.C new file mode 100644 index 0000000..2214a85 --- /dev/null +++ b/libmail/copymessage.C @@ -0,0 +1,455 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" + +#include "mail.H" +#include "copymessage.H" +#include "addmessage.H" + +#include <vector> +#include <queue> +#include <sstream> +#include <iostream> + +using namespace std; + +void mail::copyMessage::copy(mail::account *copyFrom, size_t messageNum, + mail::folder *copyTo, + mail::callback &callback, + size_t completedCount) +{ + mail::copyMessage *cm=NULL; + + try { + cm=new mail::copyMessage(copyFrom, messageNum, + copyTo, callback); + + if (!cm) + { + callback.fail("Out of memory."); + return; + } + + cm->completedCount=completedCount; + cm->go(); + + } catch (...) { + if (cm) + delete cm; + + callback.fail("An exception occurred while copying messages. The copy is aborted."); + return; + } +} + +mail::copyMessage::copyMessage(mail::account *fromArg, + size_t messageNumArg, + mail::folder *toArg, + mail::callback &callbackArg) + : callback(callbackArg), + from(fromArg), + to(toArg), + addMessagePtr(NULL), + messageNum(messageNumArg), + arrivalDate(0), + fetchCompletedCnt(0), + fetchEstimatedCnt(0), + addCompleted(0), + addEstimated(0), + completedCount(0) +{ + fetch_callback.me=this; + add_callback.me=this; + + messageInfo= fromArg->getFolderIndexInfo(messageNumArg); +} + +mail::copyMessage::FetchCallback::FetchCallback() +{ +} + +mail::copyMessage::FetchCallback::~FetchCallback() +{ +} + +void mail::copyMessage::FetchCallback::messageArrivalDateCallback(size_t dummy, + time_t dt) +{ + me->arrivalDate=dt; +} + +void mail::copyMessage::FetchCallback::messageTextCallback(size_t dummy, + string text) +{ + if (me->addMessagePtr) + { + me->addMessagePtr->saveMessageContents(text); + } +} + +void mail::copyMessage::reportProgress() +{ + callback.reportProgress((fetchCompletedCnt + addCompleted)/2, + (fetchEstimatedCnt + addEstimated)/2, + completedCount, completedCount+1); +} + +void mail::copyMessage +::FetchCallback::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + me->fetchCompletedCnt=bytesCompleted; + me->fetchEstimatedCnt=bytesEstimatedTotal; + me->reportProgress(); +} + + +void mail::copyMessage::FetchCallback::success(string dummy) +{ + (me->*successCallback)(); +} + +void mail::copyMessage::FetchCallback::fail(string errmsg) +{ + ptr<copyMessage> cm(me); + + if (me->addMessagePtr != NULL) // Report it this way + { + addMessage *p=me->addMessagePtr; + + me->addMessagePtr=NULL; + + p->fail(errmsg); + } + else + me->callback.fail(errmsg); + + + if (!cm.isDestroyed()) + { + copyMessage *p=cm; + delete p; + } + return; +} + +mail::copyMessage::AddCallback::AddCallback() +{ +} + +mail::copyMessage::AddCallback::~AddCallback() +{ +} + +void mail::copyMessage::AddCallback::success(string msg) +{ + me->addMessagePtr=NULL; + me->success(me->callback, msg); +} + +void mail::copyMessage::success(mail::callback &callback, string msg) +{ + delete this; + callback.success(msg); +} + +void mail::copyMessage::AddCallback::fail(string msg) +{ + me->addMessagePtr=NULL; + me->callback.fail(msg); + delete me; +} + +void mail::copyMessage +::AddCallback::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + me->addCompleted=bytesCompleted; + me->addEstimated=bytesEstimatedTotal; + me->reportProgress(); +} + +mail::copyMessage::~copyMessage() +{ + if (addMessagePtr) + { + mail::addMessage *p=addMessagePtr; + + addMessagePtr=NULL; + p->fail("Message copy operation aborted."); + } +} + +void mail::copyMessage::go() +{ + try { + fetch_callback.successCallback= &mail::copyMessage::fetchText; + + vector<size_t> messageNumVector; + + messageNumVector.push_back(messageNum); + + from->readMessageAttributes(messageNumVector, + mail::account::ARRIVALDATE, + fetch_callback); + } catch (...) { + callback.fail("An exception occurred while copying messages. The copy is aborted."); + delete this; + } +} + +void mail::copyMessage::fetchText() +{ + if (to.isDestroyed() || from.isDestroyed()) + { + callback.fail("Server connection closed during copy."); + delete this; + return; + } + + if (from->getFolderIndexSize() <= messageNum || + from->getFolderIndexInfo(messageNum).uid != messageInfo.uid) + { + callback.fail("Original folder's contents modified during copy"); + delete this; + } + + fetch_callback.successCallback= &mail::copyMessage::fetchCompleted; + + mail::addMessage *ptr; + + try { + ptr=to->addMessage(add_callback); + + if (ptr == NULL) + return; + + ptr->messageInfo=messageInfo; + ptr->messageDate=arrivalDate; + + } catch (...) { + callback.fail("An exception occurred while copying messages. The copy is aborted."); + delete this; + return; + } + + addMessagePtr=ptr; + + try { + vector<size_t> messageNumVector; + + messageNumVector.push_back(messageNum); + + from->readMessageContent(messageNumVector, true, + mail::readBoth, fetch_callback); + } catch (...) { + + addMessagePtr-> + fail("An exception occurred while copying messages. The copy is aborted."); + addMessagePtr=NULL; + delete this; + } +} + +void mail::copyMessage::fetchCompleted() +{ + try { + RunLater(); + } catch (...) { + + addMessagePtr-> + fail("An exception occurred while copying messages. The copy is aborted."); + addMessagePtr=NULL; + delete this; + } +} + +void mail::copyMessage::RunningLater() +{ + try { + addMessagePtr->go(); + } catch (...) { + + addMessagePtr-> + fail("An exception occurred while copying messages. The copy is aborted."); + addMessagePtr=NULL; + delete this; + } +} + +////////////////////////////////////////////////////////////////////////////// + +mail::copyMessages::Callback::Callback() +{ +} + +mail::copyMessages::Callback::~Callback() +{ +} + +void mail::copyMessages::Callback::success(string message) +{ + ++me->copiedCount; + --me->inProgress; + reportProgress(0, 0, 0, 0); + me->doSomething(message); +} + +void mail::copyMessages::Callback::fail(string message) +{ + --me->inProgress; + if (me->failMessage.size() == 0) + me->failMessage=message; + me->doSomething(message); +} + +void mail::copyMessages +::Callback::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + if (messagesCompleted >= me->progressUpdateCount) + { + me->progressUpdateCount=messagesCompleted; + me->callback.reportProgress(bytesCompleted, + bytesEstimatedTotal, + me->copiedCount, + me->totalCount); + } +} + +mail::copyMessages::copyMessages(mail::account *copyFromArg, + const vector<size_t> &messagesArg, + mail::folder *copyToArg, + mail::callback &callbackArg) + : callback(callbackArg), copyFrom(copyFromArg), + copyTo(copyToArg), inProgress(0), copiedCount(0), + totalCount(messagesArg.size()), + progressUpdateCount(0), + failMessage("") +{ + copy_callback.me=this; + + vector<size_t>::const_iterator b=messagesArg.begin(), + e=messagesArg.end(); + + while (b != e) + { + todo.push( make_pair( *b, copyFromArg + ->getFolderIndexInfo(*b).uid)); + b++; + } +} + +mail::copyMessages::~copyMessages() +{ +} + +void mail::copyMessages::RunningLater() +{ + doSomething("No messages copied."); +} + +void mail::copyMessages::doSomething(string message) +{ + while (inProgress < 2 && !todo.empty() && failMessage.size() == 0) + { + try { + size_t messageNum=todo.front().first; + string uid=todo.front().second; + + todo.pop(); + + if (copyFrom.isDestroyed() || copyTo.isDestroyed()) + { + failMessage="Server connection closed during copy."; + break; + } + + if (copyFrom->getFolderIndexSize() <= messageNum || + copyFrom->getFolderIndexInfo(messageNum).uid != uid) + { + failMessage="Original folders contents have changed in a middle of the copy operation."; + break; + } + + mail::copyMessage::copy(copyFrom, messageNum, + copyTo, copy_callback); + ++inProgress; + } catch (...) { + failMessage="An exception occurred while copying messages. The copy is aborted."; + } + } + + if (inProgress == 0 && (failMessage.size() > 0 || todo.empty())) + { + if (failMessage.size() > 0) + { + if (copiedCount > 0) + try { + string buffer; + + { + ostringstream o; + + o << copiedCount; + buffer=o.str(); + } + + failMessage += " ("; + failMessage += buffer; + failMessage += " message(s) copied)"; + } catch (...) { + } + + callback.fail(failMessage); + delete this; + } + else + success(callback, message); + } +} + +void mail::copyMessages::success(mail::callback &callback, string message) +{ + delete this; + callback.success(message); +} + +void mail::copyMessages::copy(mail::account *copyFrom, const vector<size_t> &messages, + mail::folder *copyTo, + mail::callback &callback) +{ + mail::copyMessages *cm=NULL; + + try { + cm=new mail::copyMessages(copyFrom, + messages, + copyTo, + callback); + + if (!cm) + { + callback.fail("Out of memory."); + return; + } + + cm->RunLater(); + } catch (...) { + if (cm) + delete cm; + callback.fail("An exception occurred while copying messages. The copy is aborted."); + return; + } +} diff --git a/libmail/copymessage.H b/libmail/copymessage.H new file mode 100644 index 0000000..b07875c --- /dev/null +++ b/libmail/copymessage.H @@ -0,0 +1,188 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_copymessage_H +#define libmail_copymessage_H + +#include "libmail_config.h" +#include "mail.H" +#include "objectmonitor.H" +#include "runlater.H" + +#include <vector> +#include <queue> + +//////////////////////////////////////////////////////////////////////////// +// +// This class copies a single message between two servers +// + +LIBMAIL_START + +class addMessage; + +class copyMessage : public runLater { + + mail::callback &callback; + + ptr<mail::account> from; + ptr<mail::folder> to; + + addMessage *addMessagePtr; + + size_t messageNum; + mail::messageInfo messageInfo; + time_t arrivalDate; + + copyMessage(mail::account *fromArg, + size_t messageNumArg, + mail::folder *toArg, + mail::callback &callbackArg); + virtual ~copyMessage(); + + void go(); + void fetchText(); + + class FetchCallback : public mail::callback::message { + public: + copyMessage *me; + + void (copyMessage::*successCallback)(); + + FetchCallback(); + ~FetchCallback(); + + void messageArrivalDateCallback(size_t messageNumber, + time_t datetime); + + void messageTextCallback(size_t n, std::string text); + + void success(std::string); + void fail(std::string); + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + + } fetch_callback; + + class AddCallback : public mail::callback { + public: + copyMessage *me; + + AddCallback(); + ~AddCallback(); + + void success(std::string); + void fail(std::string); + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + + } add_callback; + + size_t fetchCompletedCnt; + size_t fetchEstimatedCnt; + + size_t addCompleted; + size_t addEstimated; + + void reportProgress(); + + void fetchCompleted(); + void RunningLater(); + + void success(mail::callback &callback, std::string msg); + +public: + size_t completedCount; + + friend class FetchCallback; + friend class AddCallback; + + static void copy(mail::account *copyFrom, size_t messageNum, + mail::folder *copyTo, + mail::callback &callback, + size_t completedCount=0); +}; + +/////////////////////////////////////////////////////////////////////////// +// +// A helper class for copying a range of messages. This is done by +// instantiating multiple mail::copyMessage object. We'll try to keep +// two instances going at a time, hopefully at all times one instance +// reads from the source server, and the second instance is uploading +// to the desintation server + +class copyMessages : public runLater { + + mail::callback &callback; + + // To make sure that the source folder doesn't change, we'll + // keep track of each message's UID. + + std::queue< std::pair<size_t, std::string> > todo; + + ptr<mail::account> copyFrom; + ptr<mail::folder> copyTo; + + size_t inProgress; // # of copies in progress, at most 2. + + size_t copiedCount; + size_t totalCount; + + size_t progressUpdateCount; + + std::string failMessage; + + // Helper + + class Callback : public mail::callback { + public: + copyMessages *me; + + Callback(); + ~Callback(); + + void success(std::string message); + void fail(std::string message); + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + + + } copy_callback; + + copyMessages(mail::account *copyFrom, + const std::vector<size_t> &messages, + mail::folder *copyTo, + mail::callback &callback); + ~copyMessages(); + + void RunningLater(); + + void doSomething(std::string message); + + void success(mail::callback &callback, std::string message); + +public: + + static void copy(mail::account *copyFrom, + const std::vector<size_t> &messages, + mail::folder *copyTo, + mail::callback &callback); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/decoder.C b/libmail/decoder.C new file mode 100644 index 0000000..078ea79 --- /dev/null +++ b/libmail/decoder.C @@ -0,0 +1,16 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" + +#include "decoder.H" + +mail::decoder::decoder() +{ +} + +mail::decoder::~decoder() +{ +} diff --git a/libmail/decoder.H b/libmail/decoder.H new file mode 100644 index 0000000..bb528d7 --- /dev/null +++ b/libmail/decoder.H @@ -0,0 +1,31 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_decoder_H +#define libmail_decoder_H + +#include "libmail_config.h" +#include "namespace.H" + +#include <string> + +LIBMAIL_START + +/////////////////////////////////////////////////////////////////////////// +// +// Common superclass for objects that decode MIME encoding. (quoted-printable, +// base64). Each superclass decoder repeatedly invokes the decode() method, +// with partial decoded contents as arguments. + +class decoder { +public: + decoder(); + virtual ~decoder(); + virtual void decode(std::string)=0; +}; + +LIBMAIL_END + +#endif diff --git a/libmail/driver.C b/libmail/driver.C new file mode 100644 index 0000000..7e3397c --- /dev/null +++ b/libmail/driver.C @@ -0,0 +1,32 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ + +#include "driver.H" + +LIBMAIL_START + +extern mail::driver inbox_driver, mbox_driver, maildir_driver, + imap_driver, pop3_driver, pop3maildrop_driver, + nntp_driver, smtp_driver, tmp_driver; + +LIBMAIL_END + +static mail::driver *drivers[]={ + &mail::inbox_driver, + &mail::mbox_driver, + &mail::maildir_driver, + &mail::imap_driver, + &mail::pop3_driver, + &mail::pop3maildrop_driver, + &mail::nntp_driver, + &mail::smtp_driver, + &mail::tmp_driver, + NULL }; + +mail::driver **mail::driver::get_driver_list() +{ + return drivers; +} diff --git a/libmail/driver.H b/libmail/driver.H new file mode 100644 index 0000000..b7bcca5 --- /dev/null +++ b/libmail/driver.H @@ -0,0 +1,33 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_driver_H +#define libmail_driver_H + +#include "libmail_config.h" + +#include "namespace.H" +#include "mail.H" + +LIBMAIL_START + +class account; + +class driver { + +public: + bool (*open_func)(mail::account *&retobj, + mail::account::openInfo &oi, + mail::callback &callback, + mail::callback::disconnect &disconnectCallback); + + bool (*isRemote_func)(std::string url, bool &flag); + + static driver **get_driver_list(); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/envelope.C b/libmail/envelope.C new file mode 100644 index 0000000..f5838aa --- /dev/null +++ b/libmail/envelope.C @@ -0,0 +1,34 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "envelope.H" +#include "rfcaddr.H" + +LIBMAIL_START + +envelope::envelope() : date(0) +{ +} + +envelope::~envelope() +{ +} + +xenvelope::xenvelope() : arrivalDate(0), messageSize(0) +{ +} + +xenvelope::~xenvelope() +{ +} + +xenvelope &xenvelope::operator=(const envelope &e) +{ + ((envelope &)*this)=e; + return *this; +} + +LIBMAIL_END diff --git a/libmail/envelope.H b/libmail/envelope.H new file mode 100644 index 0000000..0227b0d --- /dev/null +++ b/libmail/envelope.H @@ -0,0 +1,49 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_envelope_h +#define libmail_envelope_h + +#include <vector> +#include <string> + +#include "namespace.H" + +// +// The message envelope information (sender/recipients, other useful stuff) + +LIBMAIL_START + +class address; + +class envelope { + +public: + envelope(); + virtual ~envelope(); + time_t date; + std::string subject; + std::vector<mail::address> from, sender, replyto, to, cc, bcc; + std::string inreplyto, messageid; + + std::vector<std::string> references; // Synthesized +}; + +// An envelope with two more fields: message arrival date, and message size + +class xenvelope : public envelope { +public: + xenvelope(); + ~xenvelope(); + + time_t arrivalDate; + unsigned long messageSize; + + xenvelope &operator=(const envelope &e); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/expungelist.C b/libmail/expungelist.C new file mode 100644 index 0000000..4d46206 --- /dev/null +++ b/libmail/expungelist.C @@ -0,0 +1,39 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ + +#include "expungelist.H" + +mail::expungeList::expungeList() +{ +} + +mail::expungeList::~expungeList() +{ +} + +void mail::expungeList::operator<<(size_t n) // Iterate in REVERSE ORDER +{ + if (!list.empty() && n + 1 == list.begin()->first) + --list.begin()->first; + else + list.insert(list.begin(), std::make_pair(n, n)); +} + +void mail::expungeList::operator>>(mail::callback::folder *cb) +{ + std::vector< std::pair<size_t, size_t> > v; + + v.reserve(list.size()); + + while (!list.empty()) + { + v.push_back(*list.begin()); + list.pop_front(); + } + + if (cb && v.size() > 0) + cb->messagesRemoved(v); +} diff --git a/libmail/expungelist.H b/libmail/expungelist.H new file mode 100644 index 0000000..5c629d3 --- /dev/null +++ b/libmail/expungelist.H @@ -0,0 +1,42 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_expungelist_H +#define libmail_expungelist_H + +#include "mail.H" +#include "namespace.H" + +#include <list> +#include <vector> + +LIBMAIL_START + +// +// Helper class: prepare a list of removed message ranges for +// mail::callback::folder::messagesRemoved. Iterating over a list of +// removed messages, IN REVERSE ORDER, this class builds a list of start-end +// ranges, which are then converted to an array. + +class expungeList { + std::list< std::pair<size_t, size_t> > list; + +public: + typedef std::list< std::pair<size_t, size_t> >::iterator iterator; + + expungeList(); + ~expungeList(); + + void operator<<(size_t); // Iterate in REVERSE ORDER + + void operator>>(mail::callback::folder *cb); + + iterator begin() { return list.begin(); } + iterator end() { return list.end(); } +}; + +LIBMAIL_END + +#endif diff --git a/libmail/fd.C b/libmail/fd.C new file mode 100644 index 0000000..1b4240b --- /dev/null +++ b/libmail/fd.C @@ -0,0 +1,850 @@ +/* +** Copyright 2002-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "config.h" +#include "mail.H" +#include "fd.H" +#include "fdtls.H" +#include "logininfo.H" +#include "soxwrap/soxwrap.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <fcntl.h> +#include <errno.h> + +#include <iostream> +#include <sstream> +#include <cstring> +#include <cstdlib> + +using namespace std; + +/////////////////////////////////////////////////////////////////////////// + +string mail::fd::rootCertRequiredErrMsg; + +mail::fd::fd(mail::callback::disconnect &disconnect_callback, + std::vector<std::string> &certificatesArg) : + mail::account(disconnect_callback), socketfd(-1), + ioDebugFlag(false), certificates(certificatesArg), writtenFlag(false), + tls(NULL) +{ +} + + +mail::fd::~fd() +{ + if (tls) + delete tls; + if (socketfd >= 0) + sox_close(socketfd); + + while (!writequeue.empty()) + { + WriteBuffer *p=writequeue.front(); + writequeue.pop(); + delete p; + } +} + +string *mail::fd::getWriteBuffer() +{ + while (!writequeue.empty()) + { + WriteBuffer *p=writequeue.front(); + + if (p->writebuffer.size() > 0) + return (&p->writebuffer); + + if (p->fillWriteBuffer()) + { + if (p->writebuffer.size() > 0) + return (&p->writebuffer); + break; // Handler's waiting for a server response + } + + writequeue.pop(); + delete p; + } + + return NULL; +} + +static void debug_io(const char *type, int socketfd, + const char *b, const char *e) +{ + ostringstream o; + + o << type << "(" << socketfd << "): "; + + while (b != e) + { + if (*b == '\r') + o << "\\r"; + else if (*b == '\n') + o << "\\n"; + else if (*b == '\t') + o << "\\t"; + else + o << (((*b) & 127) < 32 ? '.':*b); + b++; + } + o << endl; + + string s=o.str(); + + if (write(2, s.c_str(), s.size()) < 0) + ; // Ignore gcc warning +} + +#define DEBUG_IO(a,b,c) \ + do { if (ioDebugFlag) debug_io((a),(socketfd),(b),(c)); } while (0) + +string mail::fd::socketDisconnect() +{ + string errmsg=""; + + if (socketfd < 0) + return errmsg; + + DEBUG_IO("Socket.CLOSE", NULL, NULL); + +#if HAVE_LIBCOURIERTLS + + if (tls) + { + if (tls->errmsg.size() > 0) + errmsg=tls->errmsg; + + delete tls; + tls=NULL; + } +#endif + + sox_close(socketfd); + socketfd= -1; + + return errmsg; +} + +string mail::fd::socketConnect(mail::loginInfo &loginInfo, + const char *plainservice, + const char *sslservice) +{ + +#if HAVE_LIBCOURIERTLS + +#else + if (loginInfo.use_ssl) + return "SSL support not available."; + +#endif + + struct addrinfo *res; + + string server=loginInfo.server, portnum; + const char *port=loginInfo.use_ssl ? sslservice:plainservice; + size_t n=server.find(':'); + + if (n != std::string::npos) + { + port=NULL; + portnum=server.substr(n+1); + server=server.substr(0, n); + } + + if (loginInfo.options.count("debugio") > 0) + ioDebugFlag=true; + + struct addrinfo hints; + + memset(&hints, 0, sizeof(hints)); + + hints.ai_family=PF_UNSPEC; + hints.ai_socktype=SOCK_STREAM; + + int errcode=getaddrinfo(server.c_str(), port, &hints, &res); + + if (errcode) + return gai_strerror(errcode); + + if (!port) + { + int nport=atoi(portnum.c_str()); + + if (nport <= 0 || nport > 65535) + { + freeaddrinfo(res); + return strerror(EINVAL); + } + + switch (res->ai_addr->sa_family) { + case AF_INET: + ((struct sockaddr_in *)res->ai_addr) + ->sin_port=htons(nport); + break; +#ifdef AF_INET6 + case AF_INET6: + ((struct sockaddr_in6 *)res->ai_addr) + ->sin6_port=htons(nport); + break; +#endif + default: + freeaddrinfo(res); + return strerror(EAFNOSUPPORT); + } + } + + socketfd=socket(res->ai_family, res->ai_socktype, res->ai_protocol); + + connecting=1; + + // Put socket into non-blocking mode. We're 100% asynchronous. + // Set close-on-exec bit, we don't want this fd to be seen by any + // other process. + + if (socketfd < 0 || fcntl(socketfd, F_SETFL, O_NONBLOCK) < 0 || + fcntl(socketfd, F_SETFD, FD_CLOEXEC) < 0) + { + freeaddrinfo(res); + return strerror(errno); + } + +#if HAVE_LIBCOURIERTLS + + if (loginInfo.use_ssl) + { + string errmsg= starttls(loginInfo, false); + + if (errmsg.size() > 0) + { + freeaddrinfo(res); + return errmsg; + } + } +#endif + + if (sox_connect(socketfd, res->ai_addr, res->ai_addrlen) < 0) + { + freeaddrinfo(res); + if (errno != EINPROGRESS) + return strerror(errno); + + // Async connection in progress. + } + else // Managed to connect right away. + { + freeaddrinfo(res); + connecting=3; // Managed to skip some steps in process() + } + + return ""; +} + +string mail::fd::socketAttach(int fd) +{ + if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0 || + fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) + { + return strerror(errno); + } + + socketfd=fd; + connecting=3; + return ""; +} + +bool mail::fd::socketBeginEncryption(mail::loginInfo &loginInfo) +{ +#if HAVE_LIBCOURIERTLS + + string errmsg=starttls(loginInfo, true); + + if (errmsg.size() > 0) + { + loginInfo.callbackPtr->fail(errmsg); + return false; + } + + return establishtls(); + +#else + + + loginInfo.callbackPtr->fail("SSL/TLS encryption not available."); + return false; + +#endif +} + +time_t mail::fd::getTimeoutSetting(mail::loginInfo &loginInfo, + const char *name, time_t defaultValue, + time_t minValue, time_t maxValue) +{ + map<string, string>::iterator p=loginInfo.options.find(name); + + if (p != loginInfo.options.end()) + { + istringstream i(p->second); + + i >> defaultValue; + } + + if (defaultValue < minValue) + defaultValue=minValue; + + if (defaultValue > maxValue) + defaultValue=maxValue; + return defaultValue; +} + +// +// Allocate a new TLS structure. Called before any TLS/SSL negotiation +// takes place. +// + +string mail::fd::starttls(mail::loginInfo &loginInfo, + bool starttlsFlag) +{ +#if HAVE_LIBCOURIERTLS + + if (tls) + return ""; + + if ((tls=new mail::fdTLS(starttlsFlag, certificates)) == NULL) + return strerror(errno); + + tls->fd=socketfd; + loginInfo.tlsInfo=tls; + //tls->callback= loginInfo.callbackPtr; + tls->tls_info.app_data=tls; + tls->tls_info.getconfigvar= &mail::fdTLS::get_tls_config_var; + tls->tls_info.tls_err_msg= &mail::fdTLS::get_tls_err_msg; + tls->tls_info.getpemclientcert4ca= + &mail::fdTLS::get_tls_client_certs; + tls->tls_info.releasepemclientcert4ca= + &mail::fdTLS::free_tls_client_certs; + + tls->domain=""; + + if (loginInfo.options.count("novalidate-cert") == 0) + { + tls->domain=loginInfo.server; + tls->tls_info.peer_verify_domain=tls->domain.c_str(); + + const char *p=mail::fdTLS + ::get_tls_config_var("TLS_TRUSTCERTS", tls); + + if (!p || !*p) + { + if (rootCertRequiredErrMsg.size() > 0) + return rootCertRequiredErrMsg; + } + } +#endif + return ""; +} + +// +// Begin SSL/TLS negotiation. +// + +bool mail::fd::establishtls() +{ +#if HAVE_LIBCOURIERTLS + + tls->errmsg="Unable to establish a secure connection."; + if ((tls->ctx=tls_create(0, &tls->tls_info)) == NULL || + (tls->ssl=tls_connect(tls->ctx, socketfd)) == NULL) + { + string errmsg=tls->errmsg; + // mail::callback *c=tls->callback; + + delete tls; + tls=NULL; + + disconnect(errmsg.c_str()); + //if (c) + // c->fail(errmsg); + return false; + } + + tls_transfer_init(&tls->tls_transfer); +#endif + + return true; +} + +bool mail::fd::socketConnected() +{ + if (socketfd < 0) + return false; + +#if HAVE_LIBCOURIERTLS + if (tls) + tls->errmsg=""; +#endif + return true; +} + +bool mail::fd::socketEndEncryption() +{ +#if HAVE_LIBCOURIERTLS + if (tls) + { + tls_closing(&tls->tls_transfer); + return true; + } +#endif + + return false; +} + +// +// Handle any pending I/O to/from the server. +// + +int mail::fd::process(std::vector<pollfd> &fds, int &timeout) +{ + mail::pollfd myPollFd; + + char buffer[BUFSIZ]; + int n; + socklen_t s; + + bool mustReadSomething=false; + + myPollFd.fd=socketfd; + myPollFd.events=0; + myPollFd.revents=0; + + if (socketfd < 0) // Disconnected, flush anything that's written. + { + while (!writequeue.empty()) + { + WriteBuffer *p=writequeue.front(); + writequeue.pop(); + delete p; + } + return (-1); + } + + // First step in asynchronous connection logic: wait for the socket + // to become writable. + + switch (connecting) { + case 1: + myPollFd.events=pollwrite; + fds.push_back(myPollFd); + ++connecting; + return (0); + case 2: + + // Socket is writable, check the error code of the connection + // request. + + s=sizeof(n); + if (sox_getsockopt(socketfd, SOL_SOCKET, SO_ERROR, &n, &s) < 0 + || (errno=EINVAL, s) < sizeof(n) + || (errno=n) != 0) + { + timeout=0; + disconnect(strerror(errno)); + return (-1); + } + + /* FALLTHROUGH */ + + case 3: + connecting=0; + + // Step 3 - begin SSL/TLS negotation, if requested. + +#if HAVE_LIBCOURIERTLS + + if (tls) + if (!establishtls()) + { + timeout=0; + return (-1); + } +#endif + } + + string *writePtr; + +#if HAVE_LIBCOURIERTLS + + // When the connection has SSL/TLS enabled, everything gets handled + // by libcouriertls. + + if (tls) + { + if (tls->tls_transfer.writeleft == 0 && + (writePtr=getWriteBuffer()) != NULL) + { + // Transfer the next chunk of data to write to the + // TLS buffer + + tls->writebuffer= *writePtr; + tls->tls_transfer.writeleft=tls->writebuffer.size(); + tls->tls_transfer.writeptr=&tls->writebuffer[0]; + writtenBuffer(writePtr->size()); + // As far as the write queue is concerned, this + // stuff was written. + + } + + // Repeatedly call tls_transfer() until it stops doing + // something useful. + + for (;;) + { + // Set the read ptr to start of the read buffer, + // remember the current write ptr, then call + // tls_transfer() + + tls->tls_transfer.readptr=tls->readBuffer; + tls->tls_transfer.readleft=sizeof(tls->readBuffer); + + const char *save_writeptr=tls->tls_transfer.writeptr; + + int rc; + { + fd_set r, w; + + FD_ZERO(&r); + FD_ZERO(&w); + + rc=tls_transfer(&tls->tls_transfer, tls->ssl, + socketfd, &r, &w); + + if (FD_ISSET(socketfd, &r)) + myPollFd.events |= pollread; + + if (FD_ISSET(socketfd, &w)) + myPollFd.events |= pollwrite; + } + + if ((tls_isclosing(&tls->tls_transfer) || + tls_isclosed(&tls->tls_transfer)) && + !tls->tlsShutdownSent) + { + tls->tlsShutdownSent=true; + } + + readbuffer.append(tls->readBuffer, + tls->tls_transfer.readptr); + + // We might've read something. + + if (rc < 0) + { +#if 0 + cerr << "DEBUG: SSL connection terminate: buffer=" + << readbuffer.size() + << ", tls_isclosed: " << tls_isclosed(&tls->tls_transfer) + << ", errmsg=" << tls->errmsg + << endl; +#endif + + if (readbuffer.size() > 0) + { + // There's still unfinished stuff in + // readbuffer, so pretend we're happy, + // for now, but set retry to 0 seconds, + // so we go back in here to process + // more read data. + + myPollFd.events |= pollwrite; + // If we don't do that, we'll return + // without requesting any r/w activity, + // so the application will now stall. + // Let's request write status, which + // should come back immediately and + // result in -1 return from + // tls_transfer(). + + mustReadSomething=true; + + break; // Finish processing read data + } + + timeout=0; + + DEBUG_IO("EOF.SSL", NULL, NULL); + if (tls) + disconnect(tls_isclosed(&tls-> + tls_transfer) + ? NULL:strerror(errno)); + return (-1); + } + + if (save_writeptr != tls->tls_transfer.writeptr) + { + DEBUG_IO("Write.SSL",save_writeptr, + tls->tls_transfer.writeptr); + } + + if (tls->tls_transfer.readptr != tls->readBuffer) + { + DEBUG_IO("Read.SSL", + tls->readBuffer, + tls->tls_transfer.readptr); + } + + if (save_writeptr != tls->tls_transfer.writeptr || + tls->tls_transfer.readptr != tls->readBuffer) + { + timeout=0; + } + break; + } + } + else +#endif + + // Connection not encrypted + + for (;;) + { + n=sox_read(socketfd, buffer, sizeof(buffer)); + + if (n < 0) + { + if (errno == EAGAIN) + { + myPollFd.events |= pollread; + break; + } + DEBUG_IO("Read.ERROR", NULL, NULL); + timeout=0; + disconnect(strerror(errno)); + return (-1); + } + + if (n == 0) + { + timeout=0; + mustReadSomething=true; + break; + } + + if (n > 0) + DEBUG_IO("Read", buffer, buffer+n); + + readbuffer=readbuffer.append(buffer, n); + if (readbuffer.length() >= sizeof(buffer)) + { + myPollFd.events |= pollread; + break; + } + } + + + + while ( +#if HAVE_LIBCOURIERTLS + + !tls && +#endif + (writePtr=getWriteBuffer()) != NULL) + { + n= ::sox_write(socketfd, &(*writePtr)[0], writePtr->size()); + + if (n < 0) + { + DEBUG_IO("Write.ERROR", NULL, NULL); + if (errno == EAGAIN) + { + myPollFd.events |= pollwrite; + break; + } + timeout=0; + disconnect(strerror(errno)); + return (-1); + } + + if (n > 0) + { + const char *p=(*writePtr).c_str(); + + DEBUG_IO("Write", p, p+n); + } + + if (n == 0) + { + DEBUG_IO("Write.EOF", NULL, NULL); + timeout=0; + disconnect("Connection closed by remote host."); + return (-1); + } + + writtenBuffer(n); + } + + // + // Try to process any read data. + // + + MONITOR(mail::account); + + while (readbuffer.length() > 0) + { +#if 0 + static size_t skip=0; + + if (skip == 0) + { + cerr << "PROCESS: [" << readbuffer << "]" << endl; + + char linebuf[80]; + + cin.getline(linebuf, sizeof(linebuf)); + if (strcmp(linebuf, "y") == 0) + { + timeout=0; + disconnect("Forced disconnect"); + return (-1); + } + + if (isdigit(linebuf[0])) + istrstream(linebuf) >> skip; + } + else + --skip; +#endif + +#if 0 + { + string cpy=readbuffer; + + if (cpy.length() > 32) + { + cpy.erase(32); + cpy += "..."; + } + + string::iterator b=cpy.begin(), e=cpy.end(); + + while (b != e) + { + if (iscntrl((int)(unsigned char)*b)) + *b='.'; + b++; + } + + cerr << "Process: [" << cpy << "] (" + << readbuffer.length() << " bytes)" << endl; + } +#endif + + int n=socketRead(readbuffer); + + if (DESTROYED()) + { + timeout=0; + return (0); + } + + if (n <= 0) + { + timeout=0; + + if (mustReadSomething) + break; + + if (myPollFd.events) + fds.push_back(myPollFd); + return n; + } + + mustReadSomething=false; + + readbuffer.erase(0, n); + +#if 0 + if (getWriteBuffer() != NULL && socketfd >= 0) +#endif + { + timeout=0; // Callback handler wrote something + } + } +#if HAVE_LIBCOURIERTLS + if (!DESTROYED() && tls && tls_isclosing(&tls->tls_transfer) && + !tls_isclosed(&tls->tls_transfer)) + { + if (!tls->tlsShutdownSent) + myPollFd.events |= pollwrite; + else + myPollFd.events |= pollread; + readbuffer=""; + mustReadSomething=false; + } +#endif + + if (mustReadSomething) + { + DEBUG_IO("Read.EOF", NULL, NULL); + // Subclass hasn't processed any data, and it + // will never see any more data, so give up. + + disconnect("Connection closed by remote host."); + return -1; + } + + if (myPollFd.events) + fds.push_back(myPollFd); + + return 0; +} + +// Write a string. Queue it up, and wait for process() to do its job + +void mail::fd::socketWrite(string s) +{ + writtenFlag=true; + + WriteBuffer *w=new WriteBuffer; + + if (!w) + LIBMAIL_THROW("Out of memory."); + + try { + w->writebuffer=s; + writequeue.push(w); + } catch (...) { + delete w; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +void mail::fd::socketWrite(WriteBuffer *w) +{ + writtenFlag=true; + writequeue.push(w); +} + +///////////////////////////////////////////////////////////////////////////// +// +// Initialize the WriteBuffer superclass. + +mail::fd::WriteBuffer::WriteBuffer() : writebuffer("") +{ +} + +mail::fd::WriteBuffer::~WriteBuffer() +{ +} + +bool mail::fd::WriteBuffer::fillWriteBuffer() +{ + return false; +} + diff --git a/libmail/fd.H b/libmail/fd.H new file mode 100644 index 0000000..c640362 --- /dev/null +++ b/libmail/fd.H @@ -0,0 +1,191 @@ +/* +** Copyright 2002-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_fd_H +#define libmail_fd_H + +#include "mail.H" + +#include <string> +#include <queue> + +LIBMAIL_START + +/////////////////////////////////////////////////////////////////////////// +// +// A network connection-based (optionally encrypted) mail::account handler. +// +// Implements the process() method. +// +// The subclass should invoke socketWrite() to write to the network +// connection. socketWrite() can receive either a string, or a WriteBuffer. +// A WriteBuffer is subclassed to implement writing large amounts of data. +// The subclass must implement WriteBuffer::fillWriteBuffer() to save +// the next portion of data that needs to be written in the writebuffer +// field. WriteBuffer::fillWriteBuffer() gets called repeatedly each time +// the contents of writebuffer are succesfully written out. +// WriteBuffer::fillWriteBuffer() must return false when there's no more +// data to be written out, in which case the objects gets destroyed by +// mail::fd. +// +// The subclass should implement socketRead() to process read information. +// socketRead() receives everything read from the network connection, but +// not processed, and should return the number of characters that were +// processed (0 - need more input, < 0, error) +// +// The subclass should implement disconnect() which gets invoked when the +// network connection is closed. +// +// The remaining public interface is obvious. + +class fdTLS; + +class fd : public mail::account { + + int socketfd; + + bool ioDebugFlag; + + std::vector<std::string> certificates; + +protected: + bool writtenFlag; + +public: + int getfd() { return socketfd; } + + class WriteBuffer { + + public: + WriteBuffer(); + virtual ~WriteBuffer(); + + std::string writebuffer; + + virtual bool fillWriteBuffer(); + }; + + static std::string rootCertRequiredErrMsg; + // Error message returned when SSL certificate verification is + // requested, but trusted SSL certificate authority list is not + // installed. + + fd(mail::callback::disconnect &disconnect_callback, + std::vector<std::string> &certificatesArg); + + virtual ~fd(); + + std::string socketDisconnect(); + // + // Disconnect the socket. If socketDisconnect() is invoked after + // a fatal network error, a non-empty error message is returned. + + std::string socketConnect(class mail::loginInfo &loginInfo, + const char *plainservice, + const char *sslservice); + // + // Create a new server connection, should be called shortly after + // the constructor. + // + // loginInfo - login parameters. + // plainService, sslService - default ports to connect to, for SSL + // and non-SSL connections. + // + // Returns an empty string for success, or an error message. + + std::string socketAttach(int fd); + // + // Attach to an existing socket + // + + bool socketConnected(); + // + // Returns non-zero if a network connection exists (may not necessarily + // be kigged on). + // + + virtual void disconnect(const char *reason)=0; + // The subclass must implement the disconnect method. Reason is the + // error message responsible for disconnecting, or a null or an empty + // string for an orderly shut down. + + virtual int socketRead(const std::string &readbuffer)=0; + // + // The subclass must implement socketRead() to process read data in + // readbuffer. socketRead should return the number of bytes consumed + // (which may be 0, if socketRead needs to see more data before it + // can be consumed). + + void socketWrite(std::string s); + void socketWrite(WriteBuffer *p); + + // When TLS is enabled, socketBeginEncryption initiates SSL + // negotiation. The login parameters may already specify that the + // server connection should be encrypted right from the start, in + // which case fd begin SSL negotiation automatically as soon + // as the connection is established. socketEncrypted() may be used + // to determine whether the connection is already encrypted. If not, + // passing a copy of the original login parameters to + // socketBeginEncryption() will begin SSL negotiation. + + bool socketBeginEncryption(mail::loginInfo &); + + bool socketEncrypted() + { + return tls != NULL; + } + + bool socketEndEncryption(); + // + // socketEndEncryption() terminates SSL negotiation, which normally + // happens prior to disconnecting from the server. + // socketEndEncryption() returns true if an SSL connection is + // currently active, and SSL disconnection was initiated. + // The disconnect() method will be called automatically after SSL + // terminates. Typical usage: + // + // if (!socketEndEncryption()) socketDisconnect(); + + + +protected: + int process(std::vector<pollfd> &fds, int &timeout); + +private: + std::string starttls(class mail::loginInfo &loginInfo, + bool starttlsFlag); + +public: + static time_t getTimeoutSetting(mail::loginInfo &loginInfo, + const char *name, time_t defaultValue, + time_t minValue, time_t maxValue); +private: + bool establishtls(); + + int connecting; // Connection in progress + fdTLS *tls; // SSL/TLS stuff + + std::queue<WriteBuffer *> writequeue; + // Stuff waiting to be written out + + std::string *getWriteBuffer(); // Get next stuff to write + + void writtenBuffer(size_t n) // Stubb was written + { + std::string *s= &writequeue.front()->writebuffer; + + *s= s->substr(n); + } + + std::string readbuffer; // Stuff read, but not processed. + + fd(const fd &); // UNDEFINED + + fd &operator=(const fd &); // UNDEFINED +}; + +LIBMAIL_END + +#endif diff --git a/libmail/fdtls.C b/libmail/fdtls.C new file mode 100644 index 0000000..d478131 --- /dev/null +++ b/libmail/fdtls.C @@ -0,0 +1,95 @@ +/* +** Copyright 2002-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "fdtls.H" +#include <cstdlib> + +#if HAVE_LIBCOURIERTLS + +// libcouriertls.a callback - get a config setting + +const char *mail::fdTLS::get_tls_config_var(const char *varname, void *vp) +{ + return ((mail::fdTLS *)vp)->get_tls_config_var(varname); +} + +// libcouriertls.a callback - report a tls error msg + +void mail::fdTLS::get_tls_err_msg(const char *errmsg, void *vp) +{ + ((mail::fdTLS *)vp)->get_tls_err_msg(errmsg); +} + +// libcouriertls.a callback - retrieve SSL/TLS certificate + +int mail::fdTLS::get_tls_client_certs(size_t i, + const char **cert_array_ret, + size_t *cert_array_size_ret, + void *vp) +{ + return ((mail::fdTLS *)vp)->get_tls_client_certs(i, cert_array_ret, + cert_array_size_ret); +} + +// libcouriertls.a callback - release all SSL/TLS certificates + +void mail::fdTLS::free_tls_client_certs(void *vp) +{ + ((mail::fdTLS *)vp)->free_tls_client_certs(); +} + + +// Get a config setting, for now, use getenv. + +const char *mail::fdTLS::get_tls_config_var(const char *varname) +{ + if (strcmp(varname, "TLS_PROTOCOL") == 0 && tlsflag) + varname="TLS_STARTTLS_PROTOCOL"; + + if (strcmp(varname, "TLS_VERIFYPEER") == 0) + { + if (domain.size() == 0) + return "NONE"; + } + + return getenv(varname); +} + +int mail::fdTLS::get_tls_client_certs(size_t i, + const char **cert_array_ret, + size_t *cert_array_size_ret) +{ + if (i < certs.size()) + { + *cert_array_ret=certs[i].c_str(); + *cert_array_size_ret=certs[i].size(); + return 1; + } + + return 0; +} + +void mail::fdTLS::free_tls_client_certs() +{ +} + +// libcouriertls.a callback - report a tls error msg + +void mail::fdTLS::get_tls_err_msg(const char *errmsgArg) +{ + errmsg=errmsgArg; +} +#else + +mail::fdTLS::fdTLS() +{ +} + +mail::fdTLS::~fdTLS() +{ +} + +#endif diff --git a/libmail/fdtls.H b/libmail/fdtls.H new file mode 100644 index 0000000..b57cd40 --- /dev/null +++ b/libmail/fdtls.H @@ -0,0 +1,126 @@ +/* +** Copyright 2002-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_fdtls_h +#define libmail_fdtls_h + +#include "libmail_config.h" + +#include "libcouriertls.h" + +#if HAVE_LIBCOURIERTLS +#include "../tcpd/libcouriertls.h" +#endif + +#include <string> +#include <vector> +#include "namespace.H" + +LIBMAIL_START + +class callback; + +//////////////////////////////////////////////////////////////////////////// +// +// Additional SSL metadata for SSL-enabled server connections. + +class fdTLS { + +public: + +#if HAVE_LIBCOURIERTLS + + // Our read/write buffers + + char readBuffer[BUFSIZ]; + std::string writebuffer; + + // Metadata tls_transfer() needs. + + struct tls_transfer_info tls_transfer; + + // Original login or STARTTLS callback. + + //mail::callback *callback; + + // Callback data for libcouriertls.a + struct tls_info tls_info; + + // Whether we're supposed to use STARTTLS + bool tlsflag; + + // OpenSSL stuff: + ssl_context ctx; + ssl_handle ssl; + int fd; + bool tlsShutdownSent; + + std::vector<std::string> &certs; + + std::string domain; // Server's known hostname (for cert checking) + std::string errmsg; // Most recent error message. + + fdTLS(bool tlsflagArg, + std::vector<std::string> &certsArg) + : tls_info( *tls_get_default_info() ), + tlsflag(tlsflagArg), + ctx(NULL), + ssl(NULL), + fd(-1), tlsShutdownSent(false), certs(certsArg) + { + errmsg=""; + } + + ~fdTLS() + { + close(); + } + + void close() + { + if (ssl) + { + tls_disconnect(ssl, fd); + ssl=NULL; + } + + if (ctx) + { + tls_destroy(ctx); + ctx=NULL; + } + } + + static const char *get_tls_config_var(const char *, void *); + static void get_tls_err_msg(const char *, void *); + + static int get_tls_client_certs(size_t i, + const char **cert_array_ret, + size_t *cert_array_size_ret, + void *dummy_arg); + + static void free_tls_client_certs(void *dummy_arg); + +private: + const char *get_tls_config_var(const char *); + void get_tls_err_msg(const char *); + + int get_tls_client_certs(size_t i, + const char **cert_array_ret, + size_t *cert_array_size_ret); + + void free_tls_client_certs(); + +#else +public: + fdTLS(); + ~fdTLS(); + int dummy; +#endif +}; + +LIBMAIL_END + +#endif diff --git a/libmail/file.C b/libmail/file.C new file mode 100644 index 0000000..2820a72 --- /dev/null +++ b/libmail/file.C @@ -0,0 +1,191 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "file.H" +#include "unicode/unicode.h" +#include <ctype.h> + +using namespace std; + +///////////////////////////////////////////////////////////////////////////// + +mail::file::file(int fdArg, const char *mode) + : fd(dup(fdArg)), fp(NULL), pos(-1) +{ + if (fd >= 0) + fp=fdopen(fd, mode); + + if (fp && fseek(fp, 0L, SEEK_SET) < 0) + { + fclose(fp); + fp=NULL; + } +} + +mail::file::~file() +{ + if (fp != NULL) + fclose(fp); + + if (fd >= 0) + close(fd); +} + +void mail::file::seeked() +{ + if (fp) + pos=ftell(fp); +} + +string mail::file::getline() +{ + // First time through, allocate a 1000 bye buffer. + + if (buffer.size() != 1000) + { + buffer.clear(); + buffer.insert(buffer.end(), 1000, 0); + } + + string l; + + for (;;) + { + vector<char>::iterator b=buffer.begin(), e=buffer.end(); + + while (b != e) + { + int ch=getc(fp); + + if (ch == EOF) + break; + if (pos != -1) + pos++; + + *b++ = ch; + if (ch == '\n') + break; + } + + if (b == buffer.begin()) + break; // EOF + + if (b[-1] != '\n') + { + l.insert(l.end(), buffer.begin(), b); + continue; + } + + + --b; + l.insert(l.end(), buffer.begin(), b); + break; + } + + size_t n=l.size(); + + if (n > 0 && l[n-1] == '\r') + l=l.substr(0, n-1); + return l; +} + + +void mail::file::genericMessageRead(mail::account *account, + size_t messageNumber, + mail::readMode readType, + mail::callback::message &callback) +{ + ptr<mail::account> ptrAccount(account); + + if (readType == mail::readHeadersFolded + || readType == mail::readHeaders) + { + string hdr=""; + + string l; + + while (!ptrAccount.isDestroyed() && !feof(fp) && !ferror(fp)) + { + l=getline(); + + if (l.size() == 0) + break; + + if (unicode_isspace((unsigned char)l[0])) + { + const char *p=l.c_str(); + + if (readType == mail::readHeaders) + hdr += "\n"; + else // Fold headers + { + hdr += " "; + + while (*p && + unicode_isspace((unsigned char) + *p)) + p++; + } + hdr += p; + continue; + } + + if (hdr.size() > 0) // Prev header. + { + hdr += "\n"; + callback.messageTextCallback(messageNumber, + hdr); + } + hdr=l; + } + + if (hdr.size() > 0 && !ptrAccount.isDestroyed()) + { + hdr += "\n"; + callback.messageTextCallback(messageNumber, hdr); + } + return; + } + + if (readType == mail::readContents) + // Skip over the header portion. + { + while (!feof(fp) && !ferror(fp)) + { + if (getline().size() == 0) + break; + } + } + + vector<char> buffer; + + buffer.insert(buffer.end(), BUFSIZ, 0); + + size_t i=0; + + int ch; + + while (!ptrAccount.isDestroyed() && (ch=getc(fp)) != EOF) + { + if (ch == '\r') + continue; + + if (i >= buffer.size()) + { + callback.messageTextCallback(messageNumber, + string(buffer.begin(), + buffer.end())); + i=0; + } + + buffer[i++]=ch; + } + + if (i > 0 && !ptrAccount.isDestroyed()) + callback.messageTextCallback(messageNumber, + string(buffer.begin(), + buffer.begin()+i)); +} diff --git a/libmail/file.H b/libmail/file.H new file mode 100644 index 0000000..375aab7 --- /dev/null +++ b/libmail/file.H @@ -0,0 +1,62 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_file_H +#define libmail_file_H + +#include "libmail_config.h" +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> + +#include <vector> +#include <string> + +#include "namespace.H" +#include "mail.H" + +/////////////////////////////////////////////////////////////////////////// +// A wrapper for a stdio FILE object, that mail::mbox finds very useful +// +// The constructor accepts a file descriptor, and a file access mode. +// The original file descriptor is not touched, the object dupes it, and +// attaches the dupe to a FILE object. +// +// The FILE object is cleaned up by the destructor. + +#include "mail.H" + +LIBMAIL_START + +class file { + int fd; + FILE *fp; + off_t pos; + + std::vector<char> buffer; +public: + file(int fdArg, const char *mode); + ~file(); + operator FILE *() const { return fp; } + + bool operator !() const { return fp == NULL; } + + std::string getline(); // Read a line of text + + void seeked(); // The caller seeked in the file, update pos. + + off_t tell() const { return pos; } + + + void genericMessageRead(mail::account *account, + size_t messageNumber, + mail::readMode readType, + mail::callback::message &callback); +}; + +LIBMAIL_END + +#endif + diff --git a/libmail/generic.C b/libmail/generic.C new file mode 100644 index 0000000..c2971ea --- /dev/null +++ b/libmail/generic.C @@ -0,0 +1,1997 @@ +/* +** Copyright 2002-2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" + +#include "mail.H" +#include "imap.H" +#include "misc.H" +#include "generic.H" +#include "genericdecode.H" +#include "runlater.H" +#include "envelope.H" +#include "structure.H" +#include "rfcaddr.H" +#include "runlater.H" +#include "objectmonitor.H" + +#include "rfc822/rfc822.h" +#include "rfc2045/rfc2045.h" +#include "maildir/maildirkeywords.h" +#include <errno.h> +#include <ctype.h> +#include <sstream> +#include <queue> +#include <set> +#include <string> + +using namespace std; + +mail::generic::generic() +{ +} + +mail::generic::~generic() +{ +} + +////////////////////////////////////////////////////////////////////////////// +// +// mail::generic::genericAttributes() creates an Attributes object. +// +// The Attributes objects takes the original list of messages. For each +// message, genericGetMessage functions are invoked to grab the message, +// then synthesize the attributes. + +class mail::generic::Attributes : public mail::callback::message, + public mail::runLater { + + int fd; + struct rfc2045 *rfc2045p; + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + +public: + + mail::ptr<mail::account> account; // My account + mail::generic *genericInterface; // Ditto + + mail::envelope envelope; + mail::mimestruct structure; + + time_t arrivalDate; + + vector< pair<string, size_t> > messages; + + vector< pair<string, size_t> >::iterator nextMsg; + + mail::account::MessageAttributes attributes, // Attributes requested + + attributesToDo; // Attributes left to synthesize for cur msg + + mail::callback::message *callback; // Original app callback + + Attributes(mail::account *accountArg, + mail::generic *genericInterfaceArg, + const vector<size_t> &msgsArg, + mail::account::MessageAttributes attributesArg, + mail::callback::message *callbackArg); + ~Attributes(); + + void success(string msg); + void fail(string msg); + void RunningLater(); + void go(string); + + string headerBuffer; + void messageTextCallback(size_t n, string text); + + void messageSizeCallback(size_t messageNumber, + unsigned long size); +}; + +mail::generic::Attributes::Attributes(mail::account *accountArg, + mail::generic *genericInterfaceArg, + const vector<size_t> &msgsArg, + mail::account::MessageAttributes attributesArg, + mail::callback::message *callbackArg) + : account(accountArg), + genericInterface(genericInterfaceArg), + attributes(attributesArg), + callback(callbackArg) +{ + // Memorize all message numbers and uids + + vector<size_t>::const_iterator b=msgsArg.begin(), e=msgsArg.end(); + + while (b != e) + { + size_t n= *b++; + + messages.push_back( make_pair(accountArg->getFolderIndexInfo(n) + .uid, n)); + } + nextMsg=messages.begin(); + attributesToDo=attributes; +} + +mail::generic::Attributes::~Attributes() +{ + mail::callback::message *c=callback; + + callback=NULL; + + if (c) + c->fail("Server connection unexpectedly shut down."); +} + + +// See mail::Runlater + +void mail::generic::Attributes::RunningLater() +{ + go("OK"); +} + +void mail::generic::Attributes::go(string msg) +{ + while (nextMsg != messages.end() && !account.isDestroyed()) + { + if (!fixMessageNumber(account, nextMsg->first, + nextMsg->second)) // Msg disappeared? + { + attributesToDo=attributes; + nextMsg++; + continue; + } + + // Pick next attributes to synthesize + + if (attributesToDo & mail::account::MESSAGESIZE) + { + genericInterface->genericMessageSize(nextMsg->first, + nextMsg->second, + *this); + return; + } + + + if (attributesToDo & mail::account::MIMESTRUCTURE) + { + genericInterface-> + genericGetMessageFdStruct(nextMsg->first, + nextMsg->second, + true, + fd, + rfc2045p, + *this); + return; + } + + // ARRIVALDATE is synthesized from header information + // So is the envelope + + if (attributesToDo & (mail::account::ENVELOPE | mail::account::ARRIVALDATE)) + { + envelope=mail::envelope(); + arrivalDate=0; + + headerBuffer=""; + genericInterface->genericMessageRead(nextMsg->first, + nextMsg->second, + true, + mail::readHeadersFolded, + *this); + return; + } + + attributesToDo=attributes; + nextMsg++; + RunLater(); + return; + } + + // All done. + + mail::callback::message *c=callback; + + callback=NULL; + + if (c) + c->success(msg); + + delete this; + return; +} + +void mail::generic::Attributes::success(string msg) +{ + if (account.isDestroyed()) + { + delete this; + return; + } + + // Must use the same order as go(), in order to figure out which + // attribute to synthesize. The synthesized attribute is turned off. + // Lather, rinse, repeat. + + if (attributesToDo & mail::account::MESSAGESIZE) + { + attributesToDo &= ~mail::account::MESSAGESIZE; + go(msg); + return; + } + + bool goodMsg=fixMessageNumber(account, nextMsg->first, + nextMsg->second); + + if (attributesToDo & mail::account::MIMESTRUCTURE) + { + attributesToDo &= ~mail::account::MIMESTRUCTURE; + + mail::mimestruct s; + + genericMakeMimeStructure(s, fd, rfc2045p, "", NULL); + + if (callback && goodMsg) + callback->messageStructureCallback(nextMsg->second, + s); + go(msg); + return; + } + + if (attributesToDo & mail::account::ENVELOPE) + if (callback && goodMsg) + callback->messageEnvelopeCallback(nextMsg->second, + envelope); + + if ((attributesToDo & mail::account::ARRIVALDATE) && callback && goodMsg) + { + if (arrivalDate != 0) // First date in Received: hdr seen. + callback->messageArrivalDateCallback(nextMsg->second, + arrivalDate); + else if (envelope.date != 0) + callback->messageArrivalDateCallback(nextMsg->second, + envelope.date); + } + + attributesToDo &= ~(mail::account::ENVELOPE | mail::account::ARRIVALDATE); + go(msg); +} + +void mail::generic::Attributes::fail(string msg) +{ + mail::callback::message *c=callback; + + callback=NULL; + + delete this; + + if (c) + c->fail(msg); +} + +void mail::generic::Attributes::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + callback->reportProgress(bytesCompleted, bytesEstimatedTotal, + nextMsg - messages.begin(), + messages.size()); +} + +void mail::generic::Attributes::messageSizeCallback(size_t messageNumber, + unsigned long size) +{ + if (fixMessageNumber(account, nextMsg->first, + nextMsg->second)) + callback->messageSizeCallback(nextMsg->second, size); +} + +// This messageTextCallback is used for reading headers. + +void mail::generic::Attributes::messageTextCallback(size_t dummy, + string text) +{ + headerBuffer += text; + + size_t n; + + while ((n=headerBuffer.find('\n')) != std::string::npos) + { + string header=headerBuffer.substr(0, n); + + headerBuffer=headerBuffer.substr(n+1); + + n=header.find(':'); + + string value=""; + + if (n != std::string::npos) + { + size_t nsave=n++; + + while (n < header.size() && + unicode_isspace((unsigned char)header[n])) + n++; + + value=header.substr(n); + header=header.substr(0, nsave); + } + + genericBuildEnvelope(header, value, envelope); + // Accumulate envelope information + + if (strcasecmp(header.c_str(), "Received:") == 0 && + arrivalDate == 0) + { + // Attempt to synthesize arrival date, based on the + // first parsed Received: header. + + size_t n=value.size(); + + while (n > 0) + if (value[--n] == ';') + { + arrivalDate= + rfc822_parsedt(value.c_str() + + n + 1); + break; + } + } + + } +} + +// +// Accumulate header lines into the envelope structure +// + +void mail::generic::genericBuildEnvelope(string header, string value, + mail::envelope &envelope) +{ + if (strcasecmp(header.c_str(), "From") == 0) + { + size_t dummy; + + mail::address::fromString(value, envelope.from, dummy); + } + else if (strcasecmp(header.c_str(), "Sender") == 0) + { + size_t dummy; + + mail::address::fromString(value, envelope.sender, + dummy); + } + else if (strcasecmp(header.c_str(), "Reply-To") == 0) + { + size_t dummy; + + mail::address::fromString(value, envelope.replyto, + dummy); + } + else if (strcasecmp(header.c_str(), "To") == 0) + { + size_t dummy; + + mail::address::fromString(value, envelope.to, + dummy); + } + else if (strcasecmp(header.c_str(), "Cc") == 0) + { + size_t dummy; + + mail::address::fromString(value, envelope.cc, + dummy); + } + else if (strcasecmp(header.c_str(), "Bcc") == 0) + { + size_t dummy; + + mail::address::fromString(value, envelope.bcc, + dummy); + } + else if (strcasecmp(header.c_str(), "In-Reply-To") == 0) + { + envelope.inreplyto=value; + } + else if (strcasecmp(header.c_str(), "Message-ID") == 0) + { + envelope.messageid=value; + } + else if (strcasecmp(header.c_str(), "Subject") == 0) + { + envelope.subject=value; + } + else if (strcasecmp(header.c_str(), "Date") == 0) + { + envelope.date=rfc822_parsedt(value.c_str()); + } + else if (strcasecmp(header.c_str(), "References") == 0) + { + vector<address> address_list; + size_t err_index; + + address::fromString(value, address_list, err_index); + + envelope.references.clear(); + + vector<address>::iterator + b=address_list.begin(), + e=address_list.end(); + + while (b != e) + { + string s=b->getAddr(); + + if (s.size() > 0) + envelope.references + .push_back("<" + s + ">"); + ++b; + } + } +} + +////////////////////////////////////////////////////////////////////////////// +// +// A reusable class that reads a raw message from a file descriptor, and: +// +// A) gets rids of the carriage returns. +// +// B) If only the header portion is requested, stop at the first blank line. +// + +class mail::generic::CopyMimePart { + + char input_buffer[BUFSIZ]; + char output_buffer[BUFSIZ]; + +public: + CopyMimePart(); + virtual ~CopyMimePart(); + + bool copy(int fd, off_t *cnt, + mail::ptr<mail::account> &account, + mail::readMode readType, + mail::callback::message *callback); + + virtual void copyto(string)=0; // Cooked text goes here +}; + +mail::generic::CopyMimePart::CopyMimePart() +{ +} + +mail::generic::CopyMimePart::~CopyMimePart() +{ +} + +bool mail::generic::CopyMimePart::copy(int fd, // File descriptor to copy from + off_t *cnt, // Not null - max byte count + mail::ptr<mail::account> &account, + // The canary (drops dead, we're done) + + mail::readMode readType, + mail::callback::message *callback + // Original callback + ) +{ + int n=0; + size_t output_ptr=0; + char lastCh='\n'; + bool inHeaders=true; + bool foldingHeader=false; + + while (cnt == NULL || *cnt > 0) + { + if (cnt == NULL || (off_t)sizeof(input_buffer) < *cnt) + n=sizeof(input_buffer); + else + n= (int) *cnt; + + n=read(fd, input_buffer, n); + + if (n <= 0) + break; + + if (cnt) + *cnt -= n; + + int i; + + for (i=0; i<n; i++) + { + if (input_buffer[i] == '\r') + continue; + + bool endingHeaders= + inHeaders && + input_buffer[i] == '\n' && lastCh == '\n'; + + if (inHeaders ? + readType != mail::readContents: + readType == mail::readContents || + readType == mail::readBoth) + { + if (inHeaders && readType == + mail::readHeadersFolded) + // Fold headers + { + if (output_ptr > 0 && + output_buffer[output_ptr-1] + == '\n' && input_buffer[i] != '\n' + && unicode_isspace((unsigned char) + input_buffer[i])) + { + output_buffer[output_ptr-1] + =' '; + foldingHeader=true; + } + } + + if (foldingHeader && + (input_buffer[i] == '\n' || + !unicode_isspace((unsigned char) + input_buffer[i]))) + { + foldingHeader=false; + } + + if (!foldingHeader) + { + if (output_ptr >= sizeof(output_buffer) + || endingHeaders) + { + if (output_ptr > 0) + copyto(string(output_buffer, + output_buffer + + output_ptr)); + + if (account.isDestroyed()) + return false; + + output_ptr=0; + } + output_buffer[output_ptr++]=input_buffer[i]; + } + } + + if (endingHeaders) + inHeaders=false; + + lastCh=input_buffer[i]; + } + } + + if (n < 0) + return false; + + if (output_ptr > 0) + { + copyto(string(output_buffer, output_buffer + output_ptr)); + + if (account.isDestroyed()) + return false; + + if (foldingHeader) + copyto(string("\n")); + if (account.isDestroyed()) + return false; + } + return true; +} + +/////////////////////////////////////////////////////////////////////////// +// +// The ReadMultiple object implements the multiple message version of +// genericReadMessageContent(). + + +class mail::generic::ReadMultiple : public mail::callback::message, + public mail::generic::CopyMimePart, + public mail::runLater { + + mail::ptr<mail::account> account; + mail::generic *generic; + + bool peek; + enum mail::readMode readType; + + size_t completedCnt; + + mail::callback::message *callback; + + int temp_fd; + + // Callback for when genericGetMessageFd() is used + + class TempFileCallback : public mail::callback { + ReadMultiple *me; + + public: + TempFileCallback(ReadMultiple *meArg); + ~TempFileCallback(); + + void success(string); + void fail(string); + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + + }; + + TempFileCallback temp_callback; + + void copyto(string); + + string uid; + size_t messageNumber; + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + +public: + size_t totalCnt; + bool useTempFile; + // True if the contents of exactly one message were requested. + // Use genericGetMessageFd() instead of genericMessageRead(), perhaps + // the dumb driver will be able to take advantage of this. + + queue< pair<size_t, string> > messageq; // msg num/msg uid + + ReadMultiple(mail::account *accountArg, mail::generic *genericArg, + bool peekArg, + enum mail::readMode readTypeArg, + mail::callback::message &callbackArg, + size_t totalCntArg); + + ~ReadMultiple(); + + void success(string); + void fail(string); + void messageTextCallback(size_t n, string text); + void RunningLater(); + + void processTempFile(string); +}; + +mail::generic::ReadMultiple::TempFileCallback::TempFileCallback(ReadMultiple + *meArg) + : me(meArg) +{ +} + +mail::generic::ReadMultiple::TempFileCallback::~TempFileCallback() +{ +} + +void mail::generic::ReadMultiple::TempFileCallback::success(string msg) +{ + me->processTempFile(msg); +} + +void mail::generic::ReadMultiple::TempFileCallback::fail(string msg) +{ + me->fail(msg); +} + +void mail::generic::ReadMultiple +::TempFileCallback::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + me->reportProgress(bytesCompleted, bytesEstimatedTotal, + messagesCompleted, + messagesEstimatedTotal); +} + +mail::generic::ReadMultiple::ReadMultiple(mail::account *accountArg, + mail::generic *genericArg, + bool peekArg, + enum mail::readMode readTypeArg, + mail::callback::message + &callbackArg, + size_t totalCntArg) + : account(accountArg), + generic(genericArg), + peek(peekArg), + readType(readTypeArg), + completedCnt(0), + callback(&callbackArg), + temp_callback(this), + totalCnt(totalCntArg), + useTempFile(false) +{ +} + +mail::generic::ReadMultiple::~ReadMultiple() +{ +} + +void mail::generic::ReadMultiple::success(string s) +{ + completedCnt++; + + try { + if (!messageq.empty()) + { + RunLater(); + return; + } + } catch (...) { + delete this; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + mail::callback *c=callback; + callback=NULL; + + delete this; + c->success(s); +} + +void mail::generic::ReadMultiple::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + callback->reportProgress(bytesCompleted, bytesEstimatedTotal, + completedCnt, totalCnt); +} + +void mail::generic::ReadMultiple::RunningLater() +{ + try { + if (account.isDestroyed()) + { + fail("Server connection unexpectedly shut down."); + return; + } + + if (!fixMessageNumber(account, messageq.front().second, + messageq.front().first)) + { + messageq.pop(); + success("OK"); + return; + } + + uid=messageq.front().second; + messageNumber=messageq.front().first; + + messageq.pop(); + + if (!peek) + { + ptr<mail::account> a(account); + + generic->genericMarkRead(messageNumber); + + if (a.isDestroyed()) + { + fail("Aborted."); + return; + } + } + + if (!useTempFile) + generic->genericMessageRead(uid, + messageNumber, + peek, + readType, + *this); + else + generic->genericGetMessageFd(uid, + messageNumber, + peek, + temp_fd, + temp_callback); + } catch (...) { + delete this; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +void mail::generic::ReadMultiple::messageTextCallback(size_t n, + string text) +{ + callback->messageTextCallback(messageNumber, text); +} + +// Extract the read data. + +void mail::generic::ReadMultiple::processTempFile(string s) +{ + try { + if (lseek(temp_fd, 0L, SEEK_SET) < 0) + { + fail(strerror(errno)); + return; + } + + if (!copy(temp_fd, NULL, account, readType, callback)) + { + fail("Server connection unexpectedly shut down."); + return; + } + } catch (...) { + delete this; + + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + success(s); +} + +void mail::generic::ReadMultiple::copyto(string s) +{ + callback->messageTextCallback(messageNumber, s); +} + +void mail::generic::ReadMultiple::fail(string s) +{ + mail::callback *c=callback; + callback=NULL; + + delete this; + c->fail(s); +} + +////////////////////////////////////////////////////////////////////////////// +// +// This object implements the MIME section version of +// genericReadMessageContent. + +class mail::generic::ReadMimePart : public mail::callback, + public mail::generic::CopyMimePart { + + mail::ptr<mail::account> account; + mail::generic *generic; + + size_t messageNum; + + string mime_id; + + enum mail::readMode readType; + + mail::callback::message *callback; + + void copyto(string); + + bool copyHeaders(); + bool copyContents(); + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + +public: + int fd; + struct rfc2045 *rfcp; + + ReadMimePart(mail::account *account, mail::generic *generic, + size_t messageNum, + string mime_id, + enum mail::readMode readTypeArg, + mail::callback::message *callback); + + ~ReadMimePart(); + + void success(string message); + void fail(string message); +}; + +mail::generic::ReadMimePart::ReadMimePart(mail::account *accountArg, + mail::generic *genericArg, + size_t messageNumArg, + string mime_idArg, + // from mail::mimestruct + + enum mail::readMode readTypeArg, + mail::callback::message + *callbackArg) + : account(accountArg), + generic(genericArg), + messageNum(messageNumArg), + mime_id(mime_idArg), + readType(readTypeArg), + callback(callbackArg) +{ +} + +mail::generic::ReadMimePart::~ReadMimePart() +{ +} + +void mail::generic::ReadMimePart::fail(string message) +{ + mail::callback *c=callback; + + callback=NULL; + + delete this; + c->fail(message); +} + +void mail::generic::ReadMimePart::success(string message) +{ + const char *p=mime_id.c_str(); + + // Parse the synthesized MIME id, and locate the relevant rfc2045 + // tructure. + while (rfcp && *p) + { + unsigned partNumber=0; + + while (*p && isdigit((int)(unsigned char)*p)) + partNumber=partNumber * 10 + (*p++ - '0'); + + if (*p) + p++; + + rfcp=rfcp->firstpart; + + while (rfcp) + { + if (!rfcp->isdummy && --partNumber == 0) + break; + + rfcp=rfcp->next; + } + } + + bool rc=true; + + if (rfcp) + try { + rc=readType == mail::readBoth || + readType == mail::readContents + ? copyContents():copyHeaders(); + } catch (...) { + delete this; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + mail::callback *c=callback; + + callback=NULL; + + delete this; + if (rc) + c->success(message); + else + c->fail(message); +} + +// Return just the header portion. + +bool mail::generic::ReadMimePart::copyHeaders() +{ + struct rfc2045src *src=rfc2045src_init_fd(fd); + struct rfc2045headerinfo *h=src ? rfc2045header_start(src, rfcp):NULL; + int flags=RFC2045H_NOLC; + + if (readType == mail::readHeaders) + flags |= RFC2045H_KEEPNL; + + try { + char *header, *value; + + while (h && rfc2045header_get(h, &header, &value, + flags) == 0 && header) + copyto(string(header) + ": " + value + "\n"); + + if (h) + rfc2045header_end(h); + if (src) + rfc2045src_deinit(src); + } catch (...) { + if (h) + rfc2045header_end(h); + if (src) + rfc2045src_deinit(src); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + return true; +} + +// Return body end/or header. + +bool mail::generic::ReadMimePart::copyContents() +{ + off_t start_pos, end_pos, start_body; + + off_t nlines, nbodylines; + + rfc2045_mimepos(rfcp, &start_pos, &end_pos, &start_body, + &nlines, &nbodylines); + + if (lseek(fd, start_pos, SEEK_SET) < 0) + return false; + + end_pos -= start_pos; + + return copy(fd, &end_pos, account, readType, callback); +} + +void mail::generic::ReadMimePart::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + callback->reportProgress(bytesCompleted, bytesEstimatedTotal, + messagesCompleted, + messagesEstimatedTotal); +} + +void mail::generic::ReadMimePart::copyto(string s) +{ + callback->messageTextCallback(messageNum, s); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Default genericGetMessageFdStruct() implementation. First, invoke +// genericGetMessageFd(), with the following object used as the callback. +// The GetMessageFdStruct then immediately invoked genericGetMessageStruct. +// + +class mail::generic::GetMessageFdStruct : public mail::callback { + + string uid; + size_t messageNumber; + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + +public: + mail::generic *generic; + mail::callback *callback; + + int &fd; + struct rfc2045 * &rfc2045p; + + GetMessageFdStruct(string uidArg, + size_t messageNumberArg, + mail::generic *genericArg, + mail::callback *callbackArg, + int &fdArg, + struct rfc2045 *&rfc2045pArg); + ~GetMessageFdStruct(); + + void success(string); + void fail(string); +}; + +mail::generic::GetMessageFdStruct::GetMessageFdStruct(string uidArg, + size_t messageNumberArg, + mail::generic + *genericArg, + mail::callback + *callbackArg, + int &fdArg, + struct rfc2045 + *&rfc2045pArg) + : uid(uidArg), + messageNumber(messageNumberArg), + generic(genericArg), + callback(callbackArg), + fd(fdArg), + rfc2045p(rfc2045pArg) +{ + fd= -1; + rfc2045p=NULL; +} + +mail::generic::GetMessageFdStruct::~GetMessageFdStruct() +{ +} + +void mail::generic::GetMessageFdStruct::success(string message) +{ + if (rfc2045p == NULL) + { + generic->genericGetMessageStruct(uid, messageNumber, + rfc2045p, *this); + return; + } + + mail::callback *c=callback; + + callback=NULL; + delete this; + c->success(message); +} + +void mail::generic::GetMessageFdStruct::fail(string message) +{ + mail::callback *c=callback; + + callback=NULL; + delete this; + c->fail(message); +} + +void mail::generic +::GetMessageFdStruct::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + callback->reportProgress(bytesCompleted, bytesEstimatedTotal, + messagesCompleted, + messagesEstimatedTotal); +} + + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATIONS + +void mail::generic::genericGetMessageFdStruct(string uid, + size_t messageNumber, + bool peek, + int &fdRet, + struct rfc2045 *&structRet, + mail::callback &callback) +{ + GetMessageFdStruct *s=new GetMessageFdStruct(uid, messageNumber, + this, + &callback, + fdRet, + structRet); + + if (!s) + { + callback.fail("Out of memory."); + return; + } + + try { + genericGetMessageFd(uid, messageNumber, peek, fdRet, *s); + } catch (...) { + delete s; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +void mail::generic::genericAttributes(mail::account *account, + mail::generic *genericInterface, + const vector<size_t> &msgs, + mail::account::MessageAttributes attributes, + mail::callback::message &callback) +{ + Attributes *a=new Attributes(account, genericInterface, msgs, + attributes, &callback); + + if (!a) + LIBMAIL_THROW("Out of memory."); + + a->go("OK"); +} + + +bool mail::generic::fixMessageNumber(mail::account *account, + string uid, + size_t &msgNum) +{ + if (!account) + return false; + + size_t n=account->getFolderIndexSize(); + + if (n > msgNum && + account->getFolderIndexInfo(msgNum).uid == uid) + return true; + + while (n) + { + --n; + if (account->getFolderIndexInfo(n).uid == uid) + { + msgNum=n; + return true; + } + } + + return false; +} + +void mail::generic::genericReadMessageContent(mail::account *account, + mail::generic *generic, + const vector<size_t> &messages, + bool peek, + enum mail::readMode readType, + mail::callback::message + &callback) +{ + ReadMultiple *r=new ReadMultiple(account, generic, + peek, readType, + callback, messages.size()); + + if (!r) + { + callback.fail(strerror(errno)); + return; + } + + try { + vector<size_t>::const_iterator b=messages.begin(), e=messages.end(); + while (b != e) + { + size_t n= *b++; + + r->messageq.push( make_pair(n, account-> + getFolderIndexInfo(n).uid) + ); + } + + if (messages.size() == 1 && + (readType == mail::readBoth || + readType == mail::readContents || + generic->genericCachedUid(r->messageq.front().second) + )) + r->useTempFile=true; // Maybe we can cache this + + r->success("OK"); + } catch (...) { + delete r; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +// Use mail::generic::Decode to synthesize decoded content from +// genericReadMessageContent + +void mail::generic::genericReadMessageContentDecoded(mail::account *account, + mail::generic *generic, + size_t messageNum, + bool peek, + const mail::mimestruct + &info, + mail::callback::message + &callback) +{ + mail::generic::Decode *d= + new mail::generic::Decode(callback, + info.content_transfer_encoding); + + if (!d) + LIBMAIL_THROW("Out of memory."); + + try { + genericReadMessageContent(account, generic, + messageNum, peek, info, + mail::readContents, *d); + } catch (...) + { + delete d; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +void mail::generic::genericReadMessageContent(mail::account *account, + mail::generic *generic, + size_t messageNum, + bool peek, + const mail::mimestruct &msginfo, + enum mail::readMode readType, + mail::callback::message + &callback) +{ + ptr<mail::account> a(account); + + if (!peek) + generic->genericMarkRead(messageNum); + + if (a.isDestroyed()) + { + callback.fail("Aborted."); + return; + } + + ReadMimePart *m=new ReadMimePart(account, generic, messageNum, + msginfo.mime_id, + readType, + &callback); + if (!m) + { + callback.fail(strerror(errno)); + return; + } + + try { + string uid=account->getFolderIndexInfo(messageNum).uid; + + generic->genericGetMessageFdStruct(uid, messageNum, peek, + m->fd, + m->rfcp, + *m); + + } catch (...) { + delete m; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + + +/////////////////////////////////////////////////////////////////////////////// +// +// Convert rfc2045 parse structure to a mail::mimestruct. A simple +// mechanism is used to synthesize mime IDs. + +void mail::generic::genericMakeMimeStructure(mail::mimestruct &s, + int fd, + struct rfc2045 *rfcp, + string mime_id, + mail::envelope * + envelopePtr) +{ + s=mail::mimestruct(); + + s.mime_id=mime_id; + + s.type="text/plain"; // Fixed below + + s.content_transfer_encoding="8BIT"; + + // Now read the headers, and figure out the rest + + struct rfc2045src *src=rfc2045src_init_fd(fd); + struct rfc2045headerinfo *h=src ? rfc2045header_start(src, rfcp):NULL; + + char *header; + char *value; + + off_t start_pos, end_pos, start_body, nlines, nbodylines; + + rfc2045_mimepos(rfcp, &start_pos, &end_pos, &start_body, + &nlines, &nbodylines); + + s.content_size=end_pos - start_body; + s.content_lines=nbodylines; + + try { + while (h && rfc2045header_get(h, &header, &value, 0) == 0) + { + if (header == NULL) + break; + + if (strcmp(header, "content-id") == 0) + { + s.content_id=value; + continue; + } + + if (strcmp(header, "content-description") == 0) + { + s.content_description=value; + continue; + } + + if (strcmp(header, "content-transfer-encoding") == 0) + { + s.content_transfer_encoding=value; + continue; + } + + if (strcmp(header, "content-md5") == 0) + { + s.content_md5=value; + continue; + } + + if (strcmp(header, "content-language") == 0) + { + s.content_id=value; + continue; + } + + string *name; + + mail::mimestruct::parameterList *attributes; + + if (strcmp(header, "content-type") == 0) + { + name= &s.type; + attributes= &s.type_parameters; + } + else if (strcmp(header, "content-disposition") == 0) + { + name= &s.content_disposition; + attributes= &s.content_disposition_parameters; + } + else + { + if (envelopePtr) + genericBuildEnvelope(header, value, + *envelopePtr); + continue; + } + + const char *p=value; + + while (p && *p && *p != ';' && !unicode_isspace((unsigned char)*p)) + p++; + + *name= string((const char *)value, p); + + mail::upper(*name); + + while (p && *p) + { + if (unicode_isspace((unsigned char)*p) || *p == ';') + { + p++; + continue; + } + + const char *q=p; + + while (*q + && !unicode_isspace((unsigned char)*q) && *q != ';' + && *q != '=') + q++; + + string paramName=string(p, q); + + mail::upper(paramName); + + while (*q + && unicode_isspace((unsigned char)*q)) + q++; + + string paramValue=""; + + if (*q == '=') + { + q++; + + while (*q + && unicode_isspace((unsigned char)*q)) + q++; + + bool inQuote=false; + + while (*q) + { + if (!inQuote) + { + if (*q == ';') + break; + if (unicode_isspace( + (unsigned char)*q)) + break; + } + + if (*q == '"') + { + inQuote= !inQuote; + q++; + continue; + } + + if (*q == '\\' && q[1]) + ++q; + + paramValue += *q; + q++; + } + } + + attributes->set_simple(paramName, paramValue); + p=q; + } + } + } catch (...) { + if (h) + rfc2045header_end(h); + if (src) + rfc2045src_deinit(src); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + if (h) + rfc2045header_end(h); + + if (src) + rfc2045src_deinit(src); + + // Fix content type/subtype + + size_t n=s.type.find('/'); + + if (n != std::string::npos) + { + s.subtype=s.type.substr(n+1); + s.type=s.type.substr(0, n); + } + + mail::upper(s.type); + mail::upper(s.subtype); + mail::upper(s.content_transfer_encoding); + + // Now, parse the subsections + // + + mail::envelope *env=NULL; + + if (s.messagerfc822()) + env= &s.getEnvelope(); // Subsection needs an envelope + + if (mime_id.size() > 0) + mime_id += "."; + + size_t cnt=1; + + for (rfcp=rfcp->firstpart; rfcp; rfcp=rfcp->next) + { + if (rfcp->isdummy) + continue; + + string buffer; + + { + ostringstream o; + + o << cnt++; + buffer=o.str(); + } + + genericMakeMimeStructure( *s.addChild(), + fd, + rfcp, + mime_id + buffer, + env ); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// +// Generic remove messages for accounts that use mark deleted/expunge +// paradigm. + +class mail::generic::Remove : public mail::callback { + + mail::ptr<mail::account> acct; + + set<string> msgs; + + mail::callback *callback; + + void expunge(string); + void restore(string); + void done(string); + + void (mail::generic::Remove::*success_func)(string); + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + size_t messagesCompleted, + size_t messagesEstimatedTotal); + +public: + void success(string message); + void fail(string message); + + Remove(mail::account *acctArg, + const vector<size_t> &messages, + mail::callback *callbackArg); + ~Remove(); + + void mkMessageList(vector<size_t> &msgList); +}; + + +mail::generic::Remove::Remove(mail::account *acctArg, + const std::vector<size_t> &messages, + mail::callback *callbackArg) + : acct(acctArg), callback(callbackArg), + success_func(&mail::generic::Remove::expunge) +{ + vector<size_t>::const_iterator b=messages.begin(), e=messages.end(); + + size_t n= acct->getFolderIndexSize(); + + while (b != e) + { + if (*b < n) + msgs.insert(acct->getFolderIndexInfo(*b).uid); + b++; + } +} + +mail::generic::Remove::~Remove() +{ + if (callback) + { + mail::callback * volatile p=callback; + + callback=NULL; + p->success("OK"); + } +} + +void mail::generic::Remove::expunge(std::string message) +{ + success_func= &mail::generic::Remove::restore; + acct->updateFolderIndexInfo( *this ); +} + +void mail::generic::Remove::restore(std::string message) +{ + success_func= &mail::generic::Remove::done; + + vector<size_t> flipDeleted; + + mkMessageList(flipDeleted); + + if (flipDeleted.size() > 0) + { + messageInfo flags; + + flags.deleted=true; + + acct->updateFolderIndexFlags(flipDeleted, false, + true, flags, *this); + return; + } + done(message); +} + +void mail::generic::Remove::success(std::string message) +{ + if (acct.isDestroyed()) + { + delete this; + return; + } + (this->*success_func)(message); +} + +void mail::generic::Remove::fail(std::string message) +{ + if (callback) + { + mail::callback * volatile p=callback; + + callback=NULL; + p->fail(message); + } +} + +void mail::generic::Remove::done(std::string message) +{ + if (callback) + { + mail::callback * volatile p=callback; + + callback=NULL; + p->success(message); + } + delete this; +} + +void mail::generic::Remove::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + if (callback) + callback->reportProgress(bytesCompleted, + bytesEstimatedTotal, + messagesCompleted, + messagesEstimatedTotal); +} + + + +void mail::generic::Remove::mkMessageList(std::vector<size_t> &msgList) +{ + mail::account *p=acct; + + if (!p) + return; + + size_t n=p->getFolderIndexSize(); + size_t i; + + for (i=0; i<n; i++) + if (msgs.count(p->getFolderIndexInfo(i).uid) > 0) + msgList.push_back(i); +} + + +void mail::generic::genericRemoveMessages(mail::account *account, + const std::vector<size_t> &messages, + callback &cb) +{ + vector<size_t> msgToDo; + + { + set<size_t> msgSet; + + msgSet.insert(messages.begin(), messages.end()); + + size_t i, n=account->getFolderIndexSize(); + + for (i=0; i<n; i++) + { + bool toDelete=msgSet.count(i) > 0; + + if (toDelete != account->getFolderIndexInfo(i).deleted) + msgToDo.push_back(i); + } + } + + Remove *r=new Remove(account, msgToDo, &cb); + + if (!r) + { + cb.fail(strerror(errno)); + return; + } + + try + { + vector<size_t> flipDeleted; + + r->mkMessageList(flipDeleted); + + if (flipDeleted.size() > 0) + { + messageInfo flags; + + flags.deleted=true; + + account->updateFolderIndexFlags(flipDeleted, true, + false, flags, + *r); + return; + } + + r->success("OK"); + } catch (...) { + delete r; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// +// Generic 'move messages' for accounts that do not support a message 'move', +// or for moves between accounts. This is implemented as a COPY, followed by +// a Remove. + +class mail::generic::Move : public mail::callback { + + mail::ptr<mail::account> acct; + + set<string> msgs; + + mail::callback *callback; + +public: + Move(mail::account *acctArg, + const vector<size_t> &messages, + mail::callback *callbackArg); + ~Move(); + + void success(string message); + void fail(string message); + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + size_t messagesCompleted, + size_t messagesEstimatedTotal); +}; + +mail::generic::Move::Move(mail::account *acctArg, + const vector<size_t> &messages, + mail::callback *callbackArg) + : acct(acctArg), callback(callbackArg) +{ + vector<size_t>::const_iterator b=messages.begin(), e=messages.end(); + size_t n= acct->getFolderIndexSize(); + + while (b != e) + { + if (*b < n) + msgs.insert(acct->getFolderIndexInfo(*b).uid); + b++; + } +} + +mail::generic::Move::~Move() +{ + if (callback) + { + mail::callback * volatile p=callback; + + callback=NULL; + p->fail("Exception caught in generic::Move::success"); + } +} + +void mail::generic::Move::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + if (callback) + callback->reportProgress(bytesCompleted, + bytesEstimatedTotal, + messagesCompleted, + messagesEstimatedTotal); +} + +void mail::generic::Move::success(string message) +{ + if (!acct.isDestroyed() && callback) + { + vector<size_t> msgList; + + size_t n=acct->getFolderIndexSize(); + size_t i; + + for (i=0; i<n; i++) + if (msgs.count(acct->getFolderIndexInfo(i).uid) > 0) + msgList.push_back(i); + + if (msgs.size() > 0) + { + mail::callback * volatile p=callback; + mail::account * volatile a=acct; + + callback=NULL; + delete this; + + try { + a->removeMessages(msgList, *p); + } catch (...) { + p->fail("Exception caught in generic::Move::success"); + } + return; + } + } + + if (callback) + { + mail::callback * volatile p=callback; + + callback=NULL; + delete this; + p->success(message); + } +} + +void mail::generic::Move::fail(string message) +{ + if (callback) + { + mail::callback * volatile p=callback; + + callback=NULL; + delete this; + p->fail(message); + } +} + +void mail::generic::genericMoveMessages(mail::account *account, + const vector<size_t> &messages, + mail::folder *copyTo, + mail::callback &callback) +{ + Move *m=new Move(account, messages, &callback); + + if (!m) + { + callback.fail(strerror(errno)); + return; + } + + try { + account->copyMessagesTo(messages, copyTo, *m); + } catch (...) { + delete m; + } +} + +////////////////////////////////////////////////////////////////////////// + +mail::generic::updateKeywordHelper::updateKeywordHelper(const set<string> &keywordsArg, + bool setOrChangeArg, + bool changeToArg) + : keywords(keywordsArg), + setOrChange(setOrChangeArg), + changeTo(changeToArg) +{ +} + +mail::generic::updateKeywordHelper::~updateKeywordHelper() +{ +} + +bool mail::generic::updateKeywordHelper +::doUpdateKeyword(mail::keywords::Message &keyWords, + mail::keywords::Hashtable &h) +{ + if (!setOrChange) + return keyWords.setFlags(h, keywords); + + set<string>::iterator b=keywords.begin(), e=keywords.end(); + + while (b != e) + { + if (!(changeTo ? keyWords.addFlag(h, *b): + keyWords.remFlag(*b))) + return false; + ++b; + } + + return true; +} + +bool mail::generic::genericProcessKeyword(size_t messageNumber, + updateKeywordHelper &helper) +{ + return true; +} + +void mail::generic::genericUpdateKeywords(const vector<size_t> &messages, + const set<string> &keywords, + bool setOrChange, + // false: set, true: see changeTo + bool changeTo, + mail::callback::folder + *folderCallback, + mail::generic *generic, + mail::callback &cb) +{ + vector<size_t>::const_iterator b=messages.begin(), + e=messages.end(); + + updateKeywordHelper helper(keywords, setOrChange, changeTo); + + while (b != e) + { + --e; + if (!generic->genericProcessKeyword(*e, helper)) + { + cb.fail(strerror(errno)); + return; + } + } + + e=messages.end(); + + while (b != e) + folderCallback->messageChanged(*--e); + + return cb.success("Ok."); +} + + diff --git a/libmail/generic.H b/libmail/generic.H new file mode 100644 index 0000000..a1cf2c4 --- /dev/null +++ b/libmail/generic.H @@ -0,0 +1,243 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_generic_H +#define libmail_generic_H + +#include "libmail_config.h" + +/////////////////////////////////////////////////////////////////////// +// +// A primitive set of message access APIs, used by dumb drivers +// to implement the mail::account/IMAP-like message access. +// +// A driver may not have a direct implementation for some of the advanced +// methods (such as the MIME section version of readMessageContent, or +// the MIMESTRUCTURE request of readMessageAttributes). +// +// The alternative, used by the dumb drivers, is to implement the +// mail::generic interface: +// +// genericMessageRead() - read the header, or the body, or both portions of +// the message. +// +// genericMessageSize() - return just the size of the message. +// +// genericGetMessageFd(), genericGetMessageStruct(), +// genericGetMessageFdStruct() - return a descriptor of a temporary file +// with the message's contents, and/or the rfc2045 parse of the message. +// The dumb driver is expected to cache this data. The likelyhood of +// multiple calls to these functions, for the same message, is rather high, +// so the driver should act accordingly. +// The dumb driver does not need to implement genericGetMessageFdStruct, +// if not mail::generic will provide a default, PROVIDED THAT the file +// descriptor is guaranteed to remain valid after genericGetMessageFd() +// if genericGetMessageStruct() is immediately called for the same msg. TODO +// +// genericReadMessageContent()/genericReadMessageContentDecode() - a dumb +// driver can optionally implement these method. If not, mail::generic +// will use the previous three functions to do the job. +// +// genericCachedUid(std::string uid) - return true if the generics subclass +// has message UID cached (used by generics to optimize implementation). +// +// genericAttributes() - implemented entirely by mail::generic. +// +// -- +// +// fixMessageNumber() - to handle any concurrency issues, the generic +// functions also keep track of the message's UID. fixMessageNumber() +// updates the message number, based on its uid, in the event that the +// folder's contents were changed in progress. + +#include "mail.H" +#include "maildir/maildirkeywords.h" + +struct rfc2045; + +LIBMAIL_START + +class generic { + + class Attributes; + +public: + generic(); + virtual ~generic(); + + virtual void genericMessageRead(std::string uid, + size_t messageNumber, + bool peek, + mail::readMode + readType, + mail::callback::message &callback)=0; + + virtual void genericMessageSize(std::string uid, + size_t messageNumber, + mail::callback::message &callback)=0; + + virtual void genericGetMessageFd(std::string uid, + size_t messageNumber, + bool peek, + int &fdRet, + mail::callback &callback)=0; + // NOTE: fdRet gets initialized, then the callback function is invoked. + // The file descriptor remains valid ONLY UNTIL the callback function + // terminates, at which point the file descriptor may be closed. + // The callback function must dup the descriptor, if it needs to. + + virtual void genericGetMessageStruct(std::string uid, + size_t messageNumber, + struct rfc2045 *&structRet, + mail::callback &callback)=0; + // NOTE: structRet gets initialized, then the callback function is + // invoked. structRet remains valid ONLY UNTIL the callback function + // terminates, at which point the structure may get deleted. + + virtual void genericGetMessageFdStruct(std::string uid, + size_t messageNumber, + bool peek, + int &fdRet, + struct rfc2045 *&structret, + + mail::callback &callback); + + virtual bool genericCachedUid(std::string uid)=0; + + virtual void genericMarkRead(size_t messageNumber)=0; + // Mark this msg as read, invoking a callback, if needed. + + // See above. + + static void genericReadMessageContent(mail::account *account, + generic *generic, + const std::vector<size_t> + &messages, + bool peek, + enum mail::readMode readType, + mail::callback::message + &callback); + + static void genericReadMessageContent(mail::account *account, + generic *generic, + size_t messageNum, + bool peek, + const mimestruct &msginfo, + enum mail::readMode readType, + mail::callback::message + &callback); + + static void genericReadMessageContentDecoded(mail::account *account, + generic *generic, + size_t messageNum, + bool peek, + const mimestruct + &info, + mail::callback::message + &callback); + + + static void genericMoveMessages(mail::account *account, + const std::vector<size_t> &messages, + mail::folder *copyTo, + mail::callback &callback); + + // + // Generic keyword update for drivers that implement temporary + // keywords only (keywords are not saved, and are lost when the + // mail account is closed). + // + // genericUpdateKeywords() is intended to be invoked directly by + // updateKeywords(). It will dutifully iterate over the supplied + // message set, and update each message's keywords, one by one. + // + // The interface between the generic implementation, and the + // implementing driver is a bit tricky. + // The driver needs to implement the genericProcessKeyword() method. + // In the method, the driver needs to find the mail::keywords::Message + // object for message #n, and invoke the doUpdateKeyword() method, + // passing the reference to the mail::keywords::Message object as + // the sole argument. if the driver cannot locate the + // mail::keywords::Message object (can happen in the mbox driver, if + // the message was removed by another process, but the application + // hasn't noop-ed or expunged the folder). + + class updateKeywordHelper { + public: + const std::set<std::string> &keywords; + bool setOrChange; + bool changeTo; + + updateKeywordHelper(const std::set<std::string> &keywordsArg, + bool setOrChangeArg, + bool changeToArg); + ~updateKeywordHelper(); + + bool doUpdateKeyword(mail::keywords::Message &, + mail::keywords::Hashtable &); + }; + + virtual bool genericProcessKeyword(size_t messageNumber, + updateKeywordHelper &helper); + + static void genericUpdateKeywords(const std::vector<size_t> &messages, + const std::set<std::string> &keywords, + bool setOrChange, + // false: set, true: see changeTo + bool changeTo, + mail::callback::folder + *folderCallback, + generic *generic, + callback &cb); + + + +private: + class Move; + + class GetMessageFdStruct; + class ReadMultiple; + class ReadMimePart; + + class CopyMimePart; + + static void genericMakeMimeStructure(mimestruct &s, + int fd, struct rfc2045 *rfcp, + std::string mimeId, + envelope *saveEnvelope); + // + // Create a mimestruct, (and a envelope, if necessary) + // from a rfc2045 parse of a MIME section + +public: + static void genericBuildEnvelope(std::string header, std::string value, + envelope &envelope); + + class Decode; + + static void genericAttributes(mail::account *account, + generic *genericInterface, + const std::vector<size_t> &msgs, + mail::account::MessageAttributes attributes, + mail::callback::message &callback); + + static bool fixMessageNumber(mail::account *account, + std::string uid, + size_t &msgNum); + + static void genericRemoveMessages(mail::account *account, + const std::vector<size_t> &messages, + callback &cb); + + +private: + class Remove; + +}; + +LIBMAIL_END + +#endif + diff --git a/libmail/genericdecode.C b/libmail/genericdecode.C new file mode 100644 index 0000000..87f4382 --- /dev/null +++ b/libmail/genericdecode.C @@ -0,0 +1,50 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "genericdecode.H" + +using namespace std; + +mail::generic::Decode::Decode(mail::callback::message &callback, + string transferEncoding) + : mail::autodecoder(transferEncoding), + originalCallback(callback) +{ +} + +mail::generic::Decode::~Decode() +{ +} + +void mail::generic::Decode::messageTextCallback(size_t n, string text) +{ + messageNum=n; // Save the orig msg number (should never change) + decode(text); +} + +void mail::generic::Decode::reportProgress(size_t bc, size_t bt, + size_t mc, size_t mt) +{ + originalCallback.reportProgress(bc, bt, mc, mt); +} + +void mail::generic::Decode::decoded(string text) +{ + originalCallback.messageTextCallback(messageNum, text); +} + +void mail::generic::Decode::fail(string message) +{ + originalCallback.fail(message); + delete this; +} + +void mail::generic::Decode::success(string message) +{ + originalCallback.success(message); + delete this; +} + diff --git a/libmail/genericdecode.H b/libmail/genericdecode.H new file mode 100644 index 0000000..0ead3f4 --- /dev/null +++ b/libmail/genericdecode.H @@ -0,0 +1,54 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_genericdecode_H +#define libmail_genericdecode_H + +#include "libmail_config.h" +#include "mail.H" +#include "generic.H" +#include "autodecoder.H" + +#include "base64.H" +#include "qp.H" + +/////////////////////////////////////////////////////////////////////////// +// +// A helper object for the generic readMessageContentDecoded method +// implementation. The mail::autodecoder() superclass handles the actual +// decoding task. The decoded() method forwards the decoded data to the +// original application callback object. +// +// This helper object also conveniently subclasses mail::callback::message, +// nicely forwarding the retrieved MIME content to mail::autodecoder. +// It is dynamically allocated, and after its success() or fail() method is +// invoked, the message is obediently passed along to the original +// callback function, and afterwards this object destroys itself. + +class mail::generic::Decode : public mail::autodecoder, + public mail::callback::message { + + mail::callback::message &originalCallback; + +public: + Decode(mail::callback::message &callback, std::string transferEncoding); + ~Decode(); + +private: + size_t messageNum; + + void decoded(std::string buffer); + void messageTextCallback(size_t n, std::string text); + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + + void fail(std::string message); + void success(std::string message); +}; + +#endif diff --git a/libmail/headers.C b/libmail/headers.C new file mode 100644 index 0000000..813056c --- /dev/null +++ b/libmail/headers.C @@ -0,0 +1,319 @@ +/* +** Copyright 2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "rfcaddr.H" +#include "headers.H" +#include "rfc2047encode.H" +#include "rfc2047decode.H" +#include "rfc2045/rfc2045.h" +#include <errno.h> + +using namespace std; + +mail::Header::~Header() +{ +} + +string mail::Header::wrap(string s) const +{ + string ws=name + ": "; + size_t init_offset=ws.size(); + size_t offset=init_offset; + string::iterator b=s.begin(), e=s.end(); + bool first=true; + + while (b != e) + { + string::iterator c=b; + + while (c != e) + { + if (*c != ' ' && *c != '\t') + break; + ++c; + } + + while (c != e) + { + if (*c == ' ' || *c == '\t') + break; + ++c; + } + + if (c-b + offset > 76 && !first) + { + while (b != c && (*b == ' ' || *b == '\t')) + ++b; + if (b == e) + break; + + ws += "\n"; + ws.insert(ws.end(), init_offset, ' '); + offset=init_offset; + } + ws.insert(ws.end(), b, c); + offset += c-b; + b=c; + first=false; + } + return ws; +} + +mail::Header::plain::~plain() +{ +} + +mail::Header *mail::Header::plain::clone() const +{ + mail::Header::plain *c=new plain(name, text); + + if (!c) + throw strerror(errno); + return c; +} + +string mail::Header::plain::toString() const +{ + return wrap(text); +} + +string mail::Header::encoded::encode(string text, string charset, string lang) +{ + if (lang.size() > 0) + charset += "*" + lang; + + return mail::rfc2047::encode(text, charset); +} + +mail::Header::encoded::encoded(string name, + string text, string charset, string lang) + : Header::plain(name, encode(text, charset, lang)) +{ +} + +mail::Header::encoded::~encoded() +{ +} + +mail::Header::addresslist::addresslist(string name) + : mail::Header(name) +{ +} + +mail::Header::addresslist::addresslist(string name, + const std::vector<mail::emailAddress> + &addressesArg) + : mail::Header(name), addresses(addressesArg) +{ +} + +mail::Header::addresslist::~addresslist() +{ +} + +mail::Header *mail::Header::addresslist::clone() const +{ + mail::Header::addresslist *c=new addresslist(name, addresses); + + if (!c) + throw strerror(errno); + return c; +} + +std::string mail::Header::addresslist::toString() const +{ + return mail::address::toString(name + ": ", + addresses); +} + +mail::Header::mime::mime(string name) + : mail::Header(name) +{ +} + +mail::Header::mime::mime(string name, string valueArg) + : mail::Header(name), value(valueArg) +{ +} + +mail::Header::mime mail::Header::mime::fromString(string header) +{ + size_t n=header.find(':'); + + if (n != std::string::npos) + ++n; + else + n=header.size(); + + mime m(header.substr(0, n)); + + header=header.substr(n); + + rfc2045_parse_mime_header(header.c_str(), cb_type, cb_param, &m); + return m; +} + +void mail::Header::mime::cb_type(const char *t, void *void_arg) +{ + mail::Header::mime *a=(mail::Header::mime *)void_arg; + + a->value=t; + mail::upper(a->value); +} + +void mail::Header::mime::cb_param(const char *name, + const char *value, + void *void_arg) +{ + mail::Header::mime *a=(mail::Header::mime *)void_arg; + + string n=name; + + mail::upper(n); + + if (!a->parameters.exists(name)) + a->parameters.set_simple(name, value); +} + +mail::Header::mime::~mime() +{ +} + +string mail::Header::mime::toString() const +{ + string h=name + ": " + value; + size_t init_offset=name.size()+2; + + const_parameter_iterator b=begin(); + const_parameter_iterator e=end(); + + size_t offset=h.size(); + + while (b != e) + { + string w=b->first; + string s=b->second; + + string::iterator sb=s.begin(), se=s.end(); + + if (sb != se) + w += "="; + + while (sb != se) + { + if (!strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789?*+-/=_", *sb)) + break; + ++sb; + } + + if (sb == se) + { + w += s; + } + else + { + w += "\""; + + string::iterator p=s.begin(); + + for (sb=p; sb != se; sb++) + { + if (*sb == '\\' || *sb == '"') + { + w += string(p, sb) + "\\"; + w += *sb; + p=sb+1; + } + } + w += string(p, sb); + w += "\""; + } + + if (offset + w.size() > 74) + { + h += ";\n"; + h.insert(h.end(), init_offset, ' '); + offset=init_offset; + } + else + { + h += "; "; + offset += 2; + } + h += w; + offset += w.size(); + ++b; + } + return h; +} + +mail::Header *mail::Header::mime::clone() const +{ + mail::Header::mime *c=new mime(name, value); + + if (!c) + throw strerror(errno); + c->parameters=parameters; + return c; +} + +mail::Header::listitem::listitem(const listitem &hArg) + : h(hArg.h->clone()) +{ +} + +mail::Header::listitem::listitem(const mail::Header &hArg) + : h(hArg.clone()) +{ +} + +mail::Header::listitem::~listitem() +{ + delete h; +} + +mail::Header::listitem &mail::Header::listitem::operator=(const listitem &hArg) +{ + Header *p=hArg.h->clone(); + + delete h; + h=p; + return *this; +} + +mail::Header::listitem &mail::Header::listitem::operator=(const Header &hArg) +{ + Header *p=hArg.clone(); + + delete h; + h=p; + return *this; +} + +mail::Header::list::list() +{ +} + +mail::Header::list::~list() +{ +} + +mail::Header::list::operator string() const +{ + string h; + + const_iterator b=begin(); + const_iterator e=end(); + + while (b != e) + { + const Header &hh= *b; + h += hh.toString(); + h += "\n"; + ++b; + } + return h; +} diff --git a/libmail/headers.H b/libmail/headers.H new file mode 100644 index 0000000..7a0a9e1 --- /dev/null +++ b/libmail/headers.H @@ -0,0 +1,219 @@ +/* +** Copyright 2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_header_H +#define libmail_header_H + +/////////////////////////////////////////////////////////////////////////// +// +// Helper classes for parsing headers. + +#include <list> +#include <string> +#include <vector> +#include "structure.H" +#include "rfcaddr.H" +#include "namespace.H" + +LIBMAIL_START + +class emailAddress; + +// +// Superclass of all headers +// + +class Header { + +protected: + std::string wrap(std::string) const; + + class listitem; +public: + class plain; + class encoded; + class addresslist; + class mime; + class list; + + std::string name; // Header name, without the colon + + Header(std::string nameArg) : name(nameArg) {} + virtual ~Header(); + + Header(const Header &); // UNDEFINED + Header &operator=(const Header &); // UNDEFINED + + virtual std::string toString() const=0; // Convert to string format. + virtual Header *clone() const=0; // Clone myself +}; + +// +// An unstructured header, 7-bit only. +// + +class Header::plain : public Header { + + std::string text; +public: + plain(std::string name, std::string textArg) + : Header(name), text(textArg) {} + ~plain(); + + std::string toString() const; + mail::Header *clone() const; +}; + +// +// An unstructured header, MIME-encoded. +// + +class Header::encoded : public Header::plain { + + static std::string encode(std::string, std::string, std::string); +public: + encoded(std::string name, + std::string text, std::string charset, + std::string lang=""); + ~encoded(); +}; + +// +// A header containing a list of addresses. +// + +class Header::addresslist : public Header { + +public: + std::vector<mail::emailAddress> addresses; + + addresslist(std::string name); + addresslist(std::string name, + const std::vector<mail::emailAddress> &addressesArg); + ~addresslist(); + +#if 0 + addresslist &operator()(std::string name, std::string addr) + { + mail::emailAddress addr; + + addr.setDisplayName(name); + addr.setDisplayAddr(addr); + + addresses.push_back(addr); + return *this; + } +#endif + + addresslist &operator()(const mail::emailAddress &a) + { + addresses.push_back(a); + return *this; + } + + std::string toString() const; + mail::Header *clone() const; +}; + +// +// A MIME header, such as Content-Type: or Content-Description: +// + +class Header::mime : public Header { + +public: + std::string value; + mimestruct::parameterList parameters; + + typedef mimestruct::parameterList::iterator parameter_iterator; + typedef mimestruct::parameterList::const_iterator const_parameter_iterator; + + mime(std::string name); + mime(std::string name, std::string value); + + static mime fromString(std::string); +private: + static void cb_type(const char *, void *); + static void cb_param(const char *, const char *, void *); +public: + + ~mime(); + std::string toString() const; + + mime &operator()(std::string name, + std::string value) + { + parameters.set_simple(name, value); + return *this; + } + + mime &operator()(std::string name, + std::string value, + std::string charset, + std::string language="") + { + parameters.set(name, value, charset, language); + return *this; + } + + parameter_iterator begin() { return parameters.begin(); } + parameter_iterator end() { return parameters.end(); } + + const_parameter_iterator begin() const + { + return parameters.begin(); + } + + const_parameter_iterator end() const + { + return parameters.end(); + } + mail::Header *clone() const; +}; + +// +// Helper class for a list of headers. Header::listitem wraps around a +// Header superclass, and pretends to be one. +// + +class Header::listitem { + + Header *h; +public: + listitem(const listitem &); + listitem(const Header &); + ~listitem(); + listitem &operator=(const Header &); + + listitem &operator=(const listitem &); + operator Header &() { return *h; } + operator const Header &() const { return *h; } +}; + +// +// A list of headers is just a subclass of a list<Header::listitem> which +// defines a convenient string operator that returns the string representation +// of this list of headers +// + +class Header::list : public std::list<Header::listitem> { + +public: + list(); + ~list(); + + operator std::string() const; + + Header::list &operator<<(const Header &h) + { + push_back(h); + return *this; + } +}; + + +LIBMAIL_END + +#endif diff --git a/libmail/imap.C b/libmail/imap.C new file mode 100644 index 0000000..3d13270 --- /dev/null +++ b/libmail/imap.C @@ -0,0 +1,1709 @@ +/* +** Copyright 2002-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "imap.H" +#include "driver.H" +#include "smap.H" +#include "smapfetch.H" +#include "smapfetchattr.H" +#include "imapfolder.H" +#include "imaplogin.H" +#include "logininfo.H" +#include "imapfetchhandler.H" +#include "base64.H" +#include "structure.H" +#include "misc.H" +#include "genericdecode.H" + +#include <unicode/unicode.h> +#include <sstream> +#include <iomanip> +#include <algorithm> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <stdio.h> + +//#define DEBUG 1 + +using namespace std; + +///////////////////////////////////////////////////////////////////////////// + +LIBMAIL_START + +static bool open_imap(mail::account *&accountRet, + mail::account::openInfo &oi, + mail::callback &callback, + mail::callback::disconnect &disconnectCallback) +{ + mail::loginInfo imapLoginInfo; + + if (!mail::loginUrlDecode(oi.url, imapLoginInfo)) + return false; + + if (imapLoginInfo.method != "imap" && + imapLoginInfo.method != "imaps") + return false; + + accountRet=new mail::imap(oi.url, oi.pwd, + oi.certificates, + oi.loginCallbackObj, + callback, + disconnectCallback); + return true; +} + + +static bool imap_remote(string url, bool &flag) +{ + mail::loginInfo nntpLoginInfo; + + if (!mail::loginUrlDecode(url, nntpLoginInfo)) + return false; + + if (nntpLoginInfo.method != "imap" && + nntpLoginInfo.method != "imaps") + return false; + + flag=true; + return true; +} + +driver imap_driver= { &open_imap, &imap_remote }; + +LIBMAIL_END + +///////////////////////////////////////////////////////////////////////////// + +mail::imapHandler::imapHandler( int timeoutValArg) + : myimap(NULL), installedFlag(false), isBackgroundTask(false), + handlerTimeoutVal(timeoutValArg) +{ + time(&timeoutAt); +} + +// Default message handler timeout function invokes the application +// callback. + +void mail::imapHandler::callbackTimedOut(mail::callback &callback, + const char *errmsg) +{ + callback.fail(errmsg ? errmsg:"Server timed out."); +} + +mail::imapHandler::~imapHandler() +{ + if (installedFlag) // If I'm in the active handler list, take me out + myimap->remove(this); +} + +void mail::imapHandler::anotherHandlerInstalled(mail::imap &imapAccount) +{ +} + +bool mail::imapHandler::getTimeout(mail::imap &imapAccount, int &ioTimeout) +{ + ioTimeout=getTimeout(imapAccount) * 1000; + + return (ioTimeout == 0); +} + +int mail::imapHandler::getTimeout(mail::imap &imapAccount) +{ + time_t t; + + time(&t); + + if (handlerTimeoutVal == 0) + { + handlerTimeoutVal=imapAccount.timeoutSetting; + timeoutAt=t + handlerTimeoutVal; + } + + return timeoutAt > t ? timeoutAt - t:0; +} + +void mail::imapHandler::setTimeout(int t) +{ + handlerTimeoutVal=t; + setTimeout(); +} + +void mail::imapHandler::setTimeout() +{ + if (handlerTimeoutVal == 0 && myimap) + handlerTimeoutVal=myimap->timeoutSetting; + + time(&timeoutAt); + timeoutAt += handlerTimeoutVal; +} + +///////////////////////////////////////////////////////////////////////// +// +// mail::imap constructor +// + + +mail::imap::imap(string url, + string passwd, + std::vector<std::string> &certificates, + mail::loginCallback *callback_func, + mail::callback &callback, + mail::callback::disconnect &disconnectCallback) + : mail::fd(disconnectCallback, certificates), + orderlyShutdown(false), sameServerFolderPtr(NULL), + smap(false), + smapProtocolHandlerFunc(&smapHandler::singleLineProcess), + smapBinaryCount(0) +{ + cmdcounter=0; + currentFolder=NULL; + currentFetch=NULL; + wantIdleMode=false; + + mail::loginInfo loginInfo; + + loginInfo.loginCallbackFunc=callback_func; + loginInfo.callbackPtr= &callback; + + if (!loginUrlDecode(url, loginInfo)) + { + loginInfo.callbackPtr->fail("Invalid IMAP URL."); + return; + } + + + timeoutSetting=getTimeoutSetting(loginInfo, "timeout", 60, + 30, 600); + noopSetting=getTimeoutSetting(loginInfo, "noop", 600, + 5, 1800); + + noIdle=loginInfo.options.count("noidle") != 0; + loginInfo.use_ssl= loginInfo.method == "imaps"; + + if (passwd.size() > 0) + loginInfo.pwd=passwd; + + + string errmsg=socketConnect(loginInfo, "imap", "imaps"); + + if (errmsg.size() > 0) + { + loginInfo.callbackPtr->fail(errmsg); + return; + } + + // Expect a server greeting as the first order of business. + + installBackgroundTask(new mail::imapGreetingHandler(loginInfo)); +} + +mail::imap::~imap() +{ + removeAllHandlers(false, NULL); + + disconnect(); +} + +// Verbotten characters + +#if 0 +void mail::imap::mkverbotten(std::vector<unicode_char> &verbotten) +{ + // Some chars should be modutf7- + // encoded, just to be on the safe + // side. + + verbotten.push_back('/'); + verbotten.push_back('\\'); + verbotten.push_back('%'); + verbotten.push_back('*'); + verbotten.push_back(':'); + verbotten.push_back('~'); + verbotten.push_back(0); +} +#endif + +// +// Prefix an increasing counter to an imap tag, and send the entire command +// + +void mail::imap::imapcmd(string cmd, string arg) +{ + if (cmd.size() == 0) + { + socketWrite(arg); + return; + } + + if (smap) + { + socketWrite(cmd + " " + arg); + return; + } + + ostringstream o; + + cmdcounter=(cmdcounter + 1) % 1000000; + o << setw(6) << setfill('0') << cmdcounter + << cmd << " " << arg; + + socketWrite(o.str()); +} + +/////////////////////////////////////////////////////////////////////////// +// +// Install a new handler to process replies from the IMAP server +// +// If there's already an active foreground task handler, push the handler +// onto the task queue. + +void mail::imap::installForegroundTask(mail::imapHandler *handler) +{ + imapHandler *foregroundTask; + + handler->setBackgroundTask(false); + + if ((foregroundTask=hasForegroundTask()) != NULL) + { + if (!task_queue.empty()) + foregroundTask=NULL; + + task_queue.push(handler); + + if (foregroundTask) + foregroundTask->anotherHandlerInstalled(*this); + return; + } + + insertHandler(handler); +} + +mail::imapHandler *mail::imap::hasForegroundTask() +{ + handlerMapType::iterator b=handlers.begin(), e=handlers.end(); + imapHandler *h; + + while (b != e) + if (!(h=(*b++).second)->getBackgroundTask()) + return h; + return NULL; +} + +// Unconditionally install a new message handler. +// (any existing message handler with the same name is removed). + +void mail::imap::insertHandler(mail::imapHandler *handler) +{ + if (!handler) + LIBMAIL_THROW("mail::imap:: out of memory."); + + const char *name=handler->getName(); + + if (handlers.count(name) > 0) + { + mail::imapHandler *oldHandler= + &*handlers.find(name)->second; + + uninstallHandler(oldHandler); + } + + handlers.insert(make_pair(name, handler)); + handler->installedFlag=true; + handler->myimap=this; + handler->setTimeout(); + handler->installed( *this ); // Tell the handler the show's on. +} + +// Install a new message handler, and mark it as a background handler, +// so that foreground message handlers can still be installed. +// (any existing message handler with the same name is removed). + +void mail::imap::installBackgroundTask(mail::imapHandler *handler) +{ + handler->setBackgroundTask(true); + insertHandler(handler); + current_handler=handler; +} + +// Unconditionally remove a handler. The handler MUST be currently installed +// as active (it cannot be on the task queue + +void mail::imap::uninstallHandler(mail::imapHandler *handler) +{ + if (!handler->installedFlag) + { + delete handler; + return; + } + + remove(handler); + delete handler; +} + +// Remove an active handler. + +void mail::imap::remove(mail::imapHandler *handler) +{ + const char *name=handler->getName(); + + if (current_handler != NULL && + strcmp(current_handler->getName(), handler->getName()) == 0) + current_handler=NULL; // Removed the current handler. + + handlers.erase(name); + handler->installedFlag=false; + + // If we just popped off a foreground task, and there are other + // foreground tasks waiting, install the next foreground task. + + if (!handler->getBackgroundTask()) + { + if (!task_queue.empty()) + { + mail::imapHandler *nextTask=task_queue.front(); + task_queue.pop(); + + insertHandler(nextTask); + } + else if (wantIdleMode) + { + updateNotify(NULL); + } + } +} + +void mail::imap::fatalError(string errmsg) +{ + disconnect_callback.servererror(errmsg.c_str()); +} + +bool mail::imap::ready(mail::callback &callback) +{ + if (socketConnected()) + return true; + + callback.fail("Account connection closed."); + return false; +} + +void mail::imap::resumed() +{ + handlerMapType::iterator bb=handlers.begin(), + ee=handlers.end(); + + while (bb != ee) + { + if (strcasecmp(bb->first, mail::imapFOLDER::name) && + strcasecmp(bb->first, mail::smapFOLDER::name)) + bb->second->setTimeout(); + ++bb; + } +} + +/////////////////////////////////////////////////////////////////////////// +// +// Main event dispatcher. Read any response received from the IMAP server, +// and process it. Write any pending output to the imap server +// +// r, w: initialized to indicate any I/O status that must occur before +// process() should be called again. r, w should be used as arguments to +// select(2) when process() returns. + +void mail::imap::handler(vector<pollfd> &fds, int &ioTimeout) +{ + bool timedoutFlag=false; + int fd_save=getfd(); + + handlerMapType::iterator bb=handlers.begin(), + ee=handlers.end(); + + writtenFlag=false; + + MONITOR(mail::imap); + + while (bb != ee) + { + int t; + + if (bb->second->getTimeout(*this, t)) + timedoutFlag=true; + + if (t < ioTimeout) + ioTimeout=t; + bb++; + } + + if (timedoutFlag) + { + timeoutdisconnect(); + return; + } + + if (mail::fd::process(fds, ioTimeout) < 0) + return; + + if (DESTROYED()) + ioTimeout=0; + + if (DESTROYED() || getfd() < 0) + { + size_t i; + + for (i=fds.size(); i; ) + { + --i; + + if (fds[i].fd == fd_save) + { + fds.erase(fds.begin()+i, fds.begin()+i+1); + break; + } + } + return; + } + + if (writtenFlag) + ioTimeout=0; + + return; +} + +// +// Figure out where a response from the server should go. + +int mail::imap::socketRead(const string &readbuffer) +{ + MONITOR(mail::imap); + + string buffer_cpy=readbuffer; + + // Try whatever handler accepted previous folder output. + + if (current_handler != NULL) + { +#if DEBUG + cerr << "Handler: " << current_handler->getName() + << endl; +#endif + int n; + + n=current_handler->process(*this, buffer_cpy); + + +#if DEBUG + cerr << " Processed " << n << " bytes." << endl; +#endif + if (DESTROYED()) + return 0; + + if (n > 0) + return n; // The handler took the input + } + + // If the last handler didn't want it, step through the active + // handlers, until someone picks it up. + + int processed=0; + + handlerMapType::iterator bb=handlers.begin(), + ee=handlers.end(); + + while (bb != ee) + { +#if DEBUG + cerr << "Trying " << bb->second->getName() << " ... " + << endl; + + if (strcmp(bb->second->getName(), "SELECT") == 0) + { + printf("OK\n"); + } +#endif + current_handler=bb->second; + + processed=bb->second->process(*this, buffer_cpy); + + if (DESTROYED()) + return 0; + + if (processed > 0) + { +#if DEBUG + cerr << " ... processed " << processed + << " of " << readbuffer.size() + << " bytes." << endl; +#endif + break; + } + current_handler=NULL; + bb++; + } + + if (processed > 0) + return processed; + + size_t pos=buffer_cpy.find('\n'); + + if (pos == std::string::npos) + { + if (buffer_cpy.size() > 16000) + return buffer_cpy.size() - 16000; + // SANITY CHECK - DON'T LET HOSTILE SERVER DOS us + + return 0; // Read more + } + + // Ok, there are some server responses we know how to do ourselves. + + buffer_cpy.erase(pos); + + size_t processedCnt=pos+1; + + + while ((pos=buffer_cpy.find('\r')) != std::string::npos) + buffer_cpy.erase(buffer_cpy.begin() + pos, + buffer_cpy.begin() + pos + 1); + + string::iterator mb=buffer_cpy.begin(), me=buffer_cpy.end(); + + if ( mb != me && *mb == '*' ) + { + ++mb; + + string::iterator save_mb=mb; + + string msg=get_word(&mb, &me); + + upper(msg); + + if (msg == "CAPABILITY") + { + clearCapabilities(); + string status; + + while ((status=get_word(&mb, &me)).length() > 0) + setCapability(status); + return processedCnt; + } + + if (msg == "OK") + { + size_t p=buffer_cpy.find('['); + size_t q=buffer_cpy.find(']'); + string brk, brkw; + string::iterator bb, be; + + if (p != std::string::npos && q != std::string::npos && + q > p && ((brk=string(buffer_cpy.begin()+p, + buffer_cpy.begin()+q)), + bb=brk.begin(), + be=brk.end(), + brkw=get_word(&bb, &be), upper(brkw), + brkw) != "ALERT") + { + if (brkw == "PERMANENTFLAGS") + setPermanentFlags(bb, be); + + return processedCnt; + } + } + + if (msg == "NO" || msg == "OK") + { + if (serverMsgs.size() < 15) + { + if (msg == "NO") + mb=save_mb; + + serverMsgs.push_back(std::string(mb, me)); + } + + return processedCnt; + } + + if (msg != "BYE") + mb=save_mb; + + while (mb != me && unicode_isspace((unsigned char)*mb)) + ++mb; + + buffer_cpy.erase(buffer_cpy.begin(), mb); + } + + fatalError(buffer_cpy); + + if (DESTROYED()) + return 0; + + return processedCnt; +} + +void mail::imap::setPermanentFlags(string::iterator flagb, + string::iterator flage) +{ + string w; + + permanentFlags.clear(); + + while (flagb != flage && unicode_isspace((unsigned char)*flagb)) + ++flagb; + + if (flagb == flage) + return; + + if (*flagb != '(') + { + w=get_word(&flagb, &flage); + upper(w); + if (w != "NIL") + permanentFlags.insert(w); + return; + } + ++flagb; + + while ((w=get_word(&flagb, &flage)).size() > 0) + { + upper(w); + + permanentFlags.insert(w); + } +} + +void mail::imap::timeoutdisconnect() +{ + const char *errmsg="Server connection closed due to a timeout."; + + removeAllHandlers(true, errmsg); + disconnect(errmsg); +} + +void mail::imap::disconnect(const char *errmsg) +{ + string errmsg_cpy=errmsg ? errmsg:""; + + if (getfd() >= 0) + { + string errmsg2=socketDisconnect(); + + if (errmsg2.size() > 0) + errmsg_cpy=errmsg2; + + removeAllHandlers(true, errmsg_cpy.size() == 0 ? + "Connection closed by remote host." + :errmsg_cpy.c_str()); + + + if (orderlyShutdown) + errmsg_cpy=""; + else if (errmsg_cpy.size() == 0) + errmsg_cpy="Connection closed by remote host."; + + disconnect_callback.disconnected(errmsg_cpy.c_str()); + } + else + { + removeAllHandlers(true, errmsg_cpy.size() == 0 ? + "Connection closed by remote host.":errmsg); + } +} + +void mail::imap::setCapability(string status) +{ + upper(status); + capabilities.insert(status); +} + +bool mail::imap::hasCapability(string status) +{ + upper(status); + + if (status == LIBMAIL_SINGLEFOLDER) + return false; + + if (status == "ACL") // Special logic + { + if (capabilities.count("ACL") > 0) + return true; + + set<string>::iterator p; + + for (p=capabilities.begin(); p != capabilities.end(); ++p) + { + string s= *p; + upper(s); + if (strncmp(s.c_str(), "ACL2=", 5) == 0) + return true; + } + return false; + } + + return (capabilities.count(status) > 0); +} + +string mail::imap::getCapability(string name) +{ + upper(name); + + if (name == "ACL") // Special logic + { + set<string>::iterator p; + + for (p=capabilities.begin(); p != capabilities.end(); ++p) + { + string s= *p; + upper(s); + if (strncmp(s.c_str(), "ACL2=", 5) == 0) + return s; + } + if (capabilities.count("ACL") > 0) + return name; + return ""; + } + + if (name == LIBMAIL_SERVERTYPE) + { + return servertype.size() == 0 ? "imap":servertype; + } + + if (name == LIBMAIL_SERVERDESCR) + { + return serverdescr.size() == 0 ? "IMAP server":serverdescr; + } + + return (capabilities.count(name) > 0 ? "1":""); +} + +// Utility functions... + +string mail::imap::get_word(string::iterator *b, string::iterator *e) +{ + string s=""; + + while (*b != *e && unicode_isspace((unsigned char)**b)) + ++*b; + + while (*b != *e && !unicode_isspace((unsigned char)**b) && + strchr(")]", **b) == NULL) + { + s.append(&**b, 1); + ++*b; + } + return s; +} + +// IMAP tags have are prefixed with a numerical counter. Throw it away + +string mail::imap::get_cmdreply(string::iterator *b, string::iterator *e) +{ + while (*b != *e && isdigit((int)(unsigned char)**b)) + ++*b; + return (get_word(b, e)); +} + + +string mail::imap::quoteSimple(string s) +{ + string::iterator b, e; + +#if 0 + b=s.begin(); + e=s.end(); + + while (b != e) + { + if (*b >= 127 || *b < ' ') + { + string buffer; + + { + ostringstream o; + + o << "{" << s.size() << "}\r\n"; + buffer=o.str(); + } + + return buffer + s; + } + b++; + } +#endif + + string t="\""; + + b=s.begin(); + e=s.end(); + + while (b != e) + { + if (*b == '"' || *b == '\\') + t.append("\\"); + t.append(&*b, 1); + b++; + } + t.append("\""); + return t; +} + +string mail::imap::quoteSMAP(string s) +{ + string::iterator b, e; + + string t="\""; + + b=s.begin(); + e=s.end(); + + while (b != e) + { + if (*b == '"') + t.append("\""); + t.append(&*b, 1); + b++; + } + t.append("\""); + return t; +} + +// +// Remove all handlers. Possible reasons: destructor, connection timeout, +// orderly shutdown. +// + +void mail::imap::removeAllHandlers(bool timedOutFlag, // true if conn timeout + const char *errmsg) +{ + while (!handlers.empty()) + { + mail::imapHandler *handler=handlers.begin()->second; + + if (timedOutFlag) + handler->timedOut(errmsg); + + handlers.erase(handlers.begin()->first); + handler->installedFlag=false; + delete handler; + } + + while (!task_queue.empty()) + { + mail::imapHandler *p=task_queue.front(); + + task_queue.pop(); + + if (timedOutFlag) + p->timedOut(errmsg); + delete p; + } +} + +mail::imap::msgSet::msgSet() +{ +} + +mail::imap::msgSet::~msgSet() +{ +} + +// Convert an array of message numbers to imap-style msg set + +mail::imap::msgSetRange::msgSetRange(mail::imap *pArg, + const vector<size_t> &messages) + : p(pArg) +{ + msglist=messages; + + sort(msglist.begin(), msglist.end()); + nextMsg=msglist.begin(); +} + +mail::imap::msgSetRange::~msgSetRange() +{ +} + +size_t mail::imap::msgSetRange::uidNum(mail::imap *i, size_t j) +{ + if (i->currentFolder == NULL || j >= i->currentFolder->index.size()) + return 0; + + // mail::imapFolder constructs the abstract libmail.a uid as + // UIDVALIDITY/UID, so find the UID part of it. + + string uid=i->currentFolder->index[j].uid; + + size_t n=uid.find('/'); + + if (n == string::npos) + return 0; + + uid=uid.substr(n+1); + + n=0; + + istringstream(uid.c_str()) >> n; + + return n; +} + +bool mail::imap::msgSetRange::getNextRange(size_t &first, size_t &last) +{ + do + { + if (nextMsg == msglist.end()) + return false; + } while ((first=last= uidNum(p, *nextMsg++)) == 0); + + + while (nextMsg != msglist.end() && last + 1 == uidNum(p, *nextMsg)) + last = uidNum(p, *nextMsg++); + + return true; +} + +void mail::imap::messagecmd(const vector<size_t> &messages, string parameters, + string operation, + string name, + mail::callback::message &callback) +{ + msgSetRange setRange(this, messages); + + messagecmd(setRange, parameters, operation, name, callback); +} + +void mail::imap::messagecmd(msgSet &msgRange, string parameters, + string operation, + string name, + mail::callback::message &callback) +{ + if (!ready(callback)) + return; + + // Prevent excessively huge IMAP commands that result from large + // message sets. The message set is capped at about 100 characters, + // and multiple commands are generated, if this is not enough. + // + // mail::imapFetchHandler is told how many commands went out, so + // it knows to count the number of server replies. + + mail::imapFetchHandler *cmd=new mail::imapFetchHandler(callback, name); + + size_t first, last; + + string s=""; + + try { + if (!cmd) + LIBMAIL_THROW(strerror(errno)); + + while (msgRange.getNextRange(first, last)) + { + cmd->messageCntTotal += last + 1 - first; + + if (s.length() > 0) + { + if (s.length() > 100) + { + cmd->commands + .push( make_pair(name, + operation + + " " + s + + parameters + + "\r\n")); + ++cmd->counter; + s=""; + } + else + s += ","; + } + + string buffer; + + if (first != last) + { + ostringstream o; + + o << first << ":" << last; + buffer=o.str(); + } + else + { + ostringstream o; + + o << first; + buffer=o.str(); + } + + s += buffer; + } + + if (s.size() == 0) + { + delete cmd; + cmd=NULL; + callback.success("No messages to process."); + return; + } + + cmd->commands.push( make_pair(name, + operation + " " + s + parameters + + "\r\n")); + ++cmd->counter; + + installForegroundTask(cmd); + } catch (...) { + + if (cmd) + delete cmd; + + callback.fail("An exception occured while attempting to start a command."); + } +} + + + + +//////////////////////////////////////////////////////////////////////// +// + +void mail::imap::readMessageAttributes(const vector<size_t> &messages, + MessageAttributes attributes, + mail::callback::message &callback) +{ + if (smap) + { + installForegroundTask(new smapFETCHATTR(messages, + attributes, + callback, + *this)); + return; + } + + string attrList=""; + + if (attributes & ARRIVALDATE) + attrList += " INTERNALDATE"; + + if (attributes & MESSAGESIZE) + attrList += " RFC822.SIZE"; + + if (attributes & ENVELOPE) + attrList += " ENVELOPE BODY.PEEK[HEADER.FIELDS (References)]"; + + if (attributes & MIMESTRUCTURE) + attrList += " BODYSTRUCTURE"; + + if (attrList.size() == 0) + { + callback.success("No attributes were requested."); + return; + } + + attrList[0]='('; + + messagecmd(messages, " " + attrList + ")", + "UID FETCH", "FETCHENV", + callback); +} + +void mail::imap::readMessageContent(const vector<size_t> &messages, + bool peek, + enum mail::readMode readType, + mail::callback::message &callback) +{ + if (smap) + { + installForegroundTask(new smapFETCH(messages, + peek, + "", + readType, + "", + callback, + *this)); + return; + } + + doReadMessageContent(messages, peek, NULL, readType, callback); +} + +////////////////////////////////////////////////////////////////////////// +// +// There is no single RFC 2060 command that returns the headers of a +// MIME section, a blank line, then contents. Therefore, we punt: first, +// we request a .MIME, then when this command returns, we request the body. +// This is implemented using the following helper object. +// + +LIBMAIL_START + +class imapComboFetchCallback + : public mail::callback::message { + + mail::callback::message &originalCallback; + bool origPeek; + imap &origImap; + size_t origMessageNum; + + string mime_id; + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + +public: + imapComboFetchCallback(mail::callback::message &callback, + bool peek, + size_t messageNum, + const mimestruct &msginfo, + imap &imapAccount); + ~imapComboFetchCallback(); + void messageTextCallback(size_t n, string text); + void fail(string message); + void success(string message); +}; + +LIBMAIL_END + +mail::imapComboFetchCallback::imapComboFetchCallback(mail::callback::message + &callback, + bool peek, + size_t messageNum, + const mimestruct &msginfo, + imap &imapAccount) + : originalCallback(callback), origPeek(peek), origImap(imapAccount), + origMessageNum(messageNum), + mime_id(msginfo.mime_id) +{ +} + +mail::imapComboFetchCallback::~imapComboFetchCallback() +{ +} + +void mail::imapComboFetchCallback::messageTextCallback(size_t n, string text) +{ + originalCallback.messageTextCallback(n, text); +} + +void mail::imapComboFetchCallback::fail(string message) +{ + originalCallback.fail(message); + delete this; +} + +void mail::imapComboFetchCallback::success(string message) +{ + vector<size_t> messageVector; + + messageVector.push_back(origMessageNum); + + string cmd="BODY"; + + if (origPeek) + cmd="BODY.PEEK"; + + origImap.messagecmd(messageVector, + " (" + cmd + "[" + mime_id + "])", + "UID FETCH", + "CONTENTS", originalCallback); + delete this; +} + +void mail::imapComboFetchCallback::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal + ) +{ + originalCallback.reportProgress(bytesCompleted, + bytesEstimatedTotal, + messagesCompleted, + messagesEstimatedTotal); +} + +void mail::imap::readMessageContent(size_t messageNum, + bool peek, + const mail::mimestruct &msginfo, + enum mail::readMode readType, + mail::callback::message &callback) +{ + vector<size_t> messageVector; + + messageVector.push_back(messageNum); + + if (smap) + { + installForegroundTask(new smapFETCH(messageVector, + peek, + msginfo.mime_id, + readType, + "", + callback, + *this)); + return; + } + + if (readType == readBoth) + { + const mail::mimestruct *parent=msginfo.getParent(); + + if (parent != NULL && !parent->messagerfc822()) + { + // There is no RFC2060 command that returns what we + // want here: .MIME, then the body, so we punt + + mail::imapComboFetchCallback *fakeCallback=new + mail::imapComboFetchCallback(callback, + peek, + messageNum, + msginfo, + *this); + + if (!fakeCallback) + LIBMAIL_THROW("Out of memory."); + + try { + doReadMessageContent(messageVector, + peek, + &msginfo, + readHeaders, + *fakeCallback); + } catch (...) { + delete fakeCallback; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + return; + } + } + doReadMessageContent(messageVector, peek, &msginfo, + readType, + callback); +} + +// +// readMessageContentDecoded is implemented by creating a proxy +// mail::generic::Decode object, that handles MIME decoding by itself, +// then forwards the decoded data to the original callback. + +void mail::imap::readMessageContentDecoded(size_t messageNum, + bool peek, + const mail::mimestruct &msginfo, + mail::callback::message + &callback) +{ + if (smap) + { + vector<size_t> messageVector; + + messageVector.push_back(messageNum); + + installForegroundTask(new smapFETCH(messageVector, + peek, + msginfo.mime_id, + readContents, + ".DECODED", + callback, + *this)); + return; + } + + mail::generic::Decode *d= + new mail::generic::Decode(callback, + msginfo.content_transfer_encoding); + + if (!d) + LIBMAIL_THROW("Out of memory."); + + try { + readMessageContent(messageNum, peek, msginfo, + readContents, *d); + } catch (...) + { + delete d; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +// +// Helper class for folding headers read from the IMAP server. +// + +LIBMAIL_START + +class imapFolderHeadersCallback : public mail::callback::message { + + mail::callback::message &originalCallback; + + bool doFolding; + + bool prevWasNewline; + bool prevFoldingSpace; + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + +public: + imapFolderHeadersCallback(bool, mail::callback::message &); + ~imapFolderHeadersCallback(); + + void messageTextCallback(size_t n, string text); + void success(string message); + void fail(string message); +}; + +LIBMAIL_END + +mail::imapFolderHeadersCallback +::imapFolderHeadersCallback(bool doFoldingArg, + mail::callback::message &origArg) + : originalCallback(origArg), doFolding(doFoldingArg), + prevWasNewline(false), prevFoldingSpace(false) +{ +} + +mail::imapFolderHeadersCallback::~imapFolderHeadersCallback() +{ +} + +void mail::imapFolderHeadersCallback +::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + originalCallback.reportProgress(bytesCompleted, bytesEstimatedTotal, + messagesCompleted, + messagesEstimatedTotal); +} + +// Intercept the data in the callback, and use it to fold header lines. + +void mail::imapFolderHeadersCallback +::messageTextCallback(size_t n, string text) +{ + string::iterator b=text.begin(), e=text.end(), c=b; + + string cpy=""; + + while (b != e) + { + if (prevWasNewline) // Prev char was \n, which was eaten. + { + // This is the first character after a newline. + + prevWasNewline=false; + if (*b == '\n') // Blank line, ignore + { + cpy += "\n"; // The eaten newline + b++; + c=b; + continue; + } + + if ( doFolding && unicode_isspace((unsigned char)*b)) + { + // Bingo. + + prevFoldingSpace=true; + cpy += " "; // Eaten newline replaced by spc + b++; + continue; + } + + cpy += "\n"; // The eaten newline. + c=b; + b++; + continue; + } + + if (prevFoldingSpace) + { + if ( *b != '\n' && unicode_isspace((unsigned char)*b)) + { + b++; + continue; // Eating all whitespace + } + + prevFoldingSpace=false; // Resume header. + c=b; + } + + if (*b == '\n') + { + cpy.insert(cpy.end(), c, b); + prevWasNewline=true; + b++; + continue; + } + b++; + } + + if (!prevWasNewline && !prevFoldingSpace) + cpy.insert(cpy.end(), c, b); + + originalCallback.messageTextCallback(n, cpy); +} + +void mail::imapFolderHeadersCallback::success(string message) +{ + originalCallback.success(message); + delete this; +} + +void mail::imapFolderHeadersCallback::fail(string message) +{ + originalCallback.fail(message); + delete this; +} + +////// +void mail::imap::doReadMessageContent(const vector<size_t> &messages, + bool peek, + const mail::mimestruct *msginfo, + mail::readMode readType, + mail::callback::message &callback) +{ + if (!ready(callback)) + return; + + bool needHeaderDecode=false; + bool foldHeaders=false; + + string mime_id; + const mail::mimestruct *parent=msginfo ? msginfo->getParent():NULL; + + // RFC 2060 mess. + + if (readType == readBoth) + { + if (parent == NULL) + mime_id=""; + else + mime_id=parent->mime_id; + } + else if (readType == readHeadersFolded || + readType == readHeaders) + { + needHeaderDecode=true; + + if (readType == readHeadersFolded) + foldHeaders=true; + + if (parent == NULL) + mime_id="HEADER"; + else if (parent->messagerfc822()) + mime_id=parent->mime_id + ".HEADER"; + else + mime_id=msginfo->mime_id + ".MIME"; + } + else + { + // Retrieve content + + if (parent == NULL) + mime_id="TEXT"; + else if (parent->messagerfc822()) + mime_id=parent->mime_id + ".TEXT"; + else + mime_id=msginfo->mime_id; + } + + string cmd="BODY"; + + if (peek) + cmd="BODY.PEEK"; + + mail::imapFolderHeadersCallback *decodeCallback=NULL; + + if (needHeaderDecode) + { + if ((decodeCallback=new mail::imapFolderHeadersCallback + (foldHeaders, callback)) == NULL) + { + callback.fail("Out of memory."); + return; + } + } + + try { + mail::callback::message *cb= &callback; + + if (decodeCallback) + cb=decodeCallback; + + messagecmd(messages, + " (" + cmd + "[" + mime_id + "])", "UID FETCH", + "CONTENTS", *cb); + + } catch (...) { + if (decodeCallback) + delete decodeCallback; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +///////////////////////////////////////////////////////////////////////// +// +// Helper class for most commands. Determines whether the read line of +// input contains an untagged message (which results in untaggedMessage() +// getting invoked), or a tagged message (which results in taggedMessage() +// getting invoked. + +mail::imapCommandHandler::imapCommandHandler(int timeout) + : mail::imapHandler(timeout) +{ +} + +mail::imapCommandHandler::~imapCommandHandler() +{ +} + +int mail::imapCommandHandler::process(mail::imap &imapAccount, string &buffer) +{ + string::iterator b=buffer.begin(); + string::iterator e=buffer.end(); + + while (b != e) + { + if (unicode_isspace((unsigned char)*b)) + { + b++; + continue; + } + if (*b != '*') + break; + + b++; + + string w=imapAccount.get_word(&b, &e); + + if (b == e) + break; + + size_t cnt= b - buffer.begin(); + + mail::upper(w); + + if (untaggedMessage(imapAccount, w)) + return (cnt); + return (0); + } + + size_t p=buffer.find('\n'); + + if (p == std::string::npos) + return (0); + + string buffer_cpy=buffer; + + buffer_cpy.erase(p); + + size_t stripcr; + + while ((stripcr=buffer_cpy.find('\r')) != std::string::npos) + buffer_cpy.erase(buffer_cpy.begin() + stripcr, + buffer_cpy.begin() + stripcr + 1); + + if (buffer_cpy.size() > 0 && + buffer_cpy[0] == (imapAccount.smap ? '>':'+')) + return continuationRequest(imapAccount, buffer_cpy) ? p+1:0; + + b=buffer_cpy.begin(); + e=buffer_cpy.end(); + + string w=imapAccount.get_cmdreply(&b, &e); + + upper(w); + + if (imapAccount.smap) + { + while (b != e && unicode_isspace((unsigned char)*b)) + b++; + + buffer_cpy.erase(0, b - buffer_cpy.begin()); + + return taggedMessage(imapAccount, + w, + buffer_cpy, + w == "+OK", + buffer_cpy) ? p+1:0; + } + + buffer_cpy.erase(0, b - buffer_cpy.begin()); + + string errmsg=buffer_cpy; + + b=errmsg.begin(); + e=errmsg.end(); + + string okfailWord=imapAccount.get_word(&b, &e); + + upper(okfailWord); + + while (b != e && unicode_isspace((unsigned char)*b)) + b++; + + errmsg.erase(0, b-errmsg.begin()); + + b=errmsg.begin(); + e=errmsg.end(); + + if (b != e && *b == '[') // Drop OK [APPENDUID ...], and friends + { + while (b != e && *b != ']') + b++; + + if (b != e) + b++; + + while (b != e && unicode_isspace((unsigned char)*b)) + b++; + errmsg.erase(0, b-errmsg.begin()); + } + + ptr<mail::imap> acctPtr= &imapAccount; + + int n=taggedMessage(imapAccount, w, buffer_cpy, + okfailWord == "OK", errmsg) ? p+1:0; + + // Report any accumulated server messages in the response + + if (!acctPtr.isDestroyed() && acctPtr->serverMsgs.size() > 0) + { + vector<string>::iterator b, e; + + b=acctPtr->serverMsgs.begin(); + e=acctPtr->serverMsgs.end(); + + errmsg=""; + + while (b != e) + { + if (errmsg.size() > 0) + errmsg += "\n"; + errmsg += *b++; + } + acctPtr->serverMsgs.clear(); + acctPtr->fatalError(errmsg); + } + return n; +} + +// Subclasses must explicitly declare their intention to handle continuation +// requests. + +bool mail::imapCommandHandler::continuationRequest(mail::imap &imapAccount, + string request) +{ + return false; +} diff --git a/libmail/imap.H b/libmail/imap.H new file mode 100644 index 0000000..e305653 --- /dev/null +++ b/libmail/imap.H @@ -0,0 +1,526 @@ +/* +** Copyright 2002-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_imap_H +#define libmail_imap_H + +#include "libmail_config.h" +#if HAVE_FCNTL_H +#include <fcntl.h> +#endif +#include "mail.H" +#include <sys/types.h> +#include <time.h> +#include <map> +#include <set> +#include <queue> + +#include "imapfolders.H" +#include "unicode/unicode.h" +#include "maildir/maildirkeywords.h" +#include "fd.H" + +/////////////////////////////////////////////////////////////////////////// +// +// An IMAP implementation. Probably the most sophisticated libmail driver, +// primarily because it directly implements every request, instead of +// fobbing off the task to libgeneric. +// + +#define IMAP_UWIMAP "uwimap" +#define IMAP_COURIER "courier" +#define IMAP_CYRUS "cyrus" // TODO + +LIBMAIL_START + +class imapSYNCHRONIZE; + +class smapHandler; +class smapAddMessage; + +class imap : public fd { + +private: + + bool orderlyShutdown; // true - orderly shutdown in progress + +public: + time_t timeoutSetting; + time_t noopSetting; + bool noIdle; // Manual override - do not use IMAP IDLE + + const imapFolder *sameServerFolderPtr; + // helper fld to detect folders linked + // to this server + + bool smap; + + int (smapHandler::*smapProtocolHandlerFunc)(imap &, std::string &); + unsigned long smapBinaryCount; + +private: + + int socketRead(const std::string &readbuffer); + + struct ltstr { + bool operator()(const char *a, const char *b) const + { + return (strcmp(a,b) < 0); + } + }; + +public: + bool ready(mail::callback &callback); + + void serverError(const char *errmsg) + { + disconnect_callback.servererror(errmsg); + } + + // A number of "handlers" are active at any time, ready to process + // server responses. Each handler is identified by a unique name, + // and all handlers are saved in the following map. + + typedef std::map<const char*, class imapHandler *, ltstr> + handlerMapType; + + handlerMapType handlers; + + friend class imapFOLDER_COUNT; + friend class imapSELECT_FLAGS; + friend class imapSELECT_OK; + friend class imapSELECT; +private: + + std::set<std::string> capabilities; // Server capabilities + std::set<std::string> folderFlags; // Current folder's available flags + + std::set<std::string> permanentFlags; + void setPermanentFlags(std::string::iterator flagb, + std::string::iterator flage); + + class imapHandler *current_handler; + // Handler the last processed server output. + + +public: + void fatalError(std::string errmsg); // server connection failed +private: + size_t cmdcounter; // Counter to generate unique IMAP cmd tags. + + // An IMAP cmdtag consists of a unique counter portion, followed by + // handler's name, so when we receive a server response we know which + // handler is responsible. + +public: + + // INTERNAL FUNCTIONS + + mail::keywords::Hashtable keywordHashtable; // Keyword support + + bool keywordAllowed(std::string); + + class imapFOLDERinfo *currentFolder; // Currently open folder + imapSYNCHRONIZE *currentSYNCHRONIZE; // Current synchronization + + class imapFetchHandler *currentFetch; + // Current fetch reply in progress. + + bool wantIdleMode; // True if we want to be in IDLE mode, now + + void updateNotify(callback *callbackArg); + + std::queue<imapHandler *> task_queue; + // Waiting handlers that will be added to the active handler map + // as soon as all the existing handlers are gone. + + void installBackgroundTask(imapHandler *handler); + // Install a new handler unconditionally. Typically, the handler + // understands server messages not particular to any command + // (* EXPUNGE, et al...) + + void installForegroundTask(imapHandler *handler); + // Install a pending task, typically a command. The handler + // is placed on the task_queue if any other foreground handler is + // already present. When the foreground handler is done, the first + // task is popped off the task_queue, to replace it. + + void uninstallHandler(imapHandler *handler); + // The handler's probably done its job. + + void removeAllHandlers(bool timedOutFlag, const char *errmsg); + // Possibly a fatal error. + // timedOutFlag - handlers are removed due to server timeout + // (all handler's timedOut() method is invoked). timedOutFlag + // is false is set when removeAllHandlers() is invoked from the + // destructor, where we want to clean up only, and we better not + // trigger any more activity. + + void remove(imapHandler *handler); + // Remove a specific handler from the handler map. + +private: + void insertHandler(imapHandler *handler); + // Add a handler to the handler map + +public: + imapHandler *hasForegroundTask(); + // Indicate whether any foreground tasks are running + + void imapcmd(std::string cmd, std::string arg); + // Write a command to the server. + + void disconnect() + { + disconnect(NULL); + } + +private: + void timeoutdisconnect(); +public: + void disconnect(const char *errmsg); + + void setCapability(std::string capability); + void clearCapabilities() { capabilities.clear(); } + // Utility functions + + static std::string get_word(std::string::iterator *b, std::string::iterator *e); + static std::string get_cmdreply(std::string::iterator *b, std::string::iterator *e); + + static std::string quoteSimple(std::string s); + static std::string quoteSMAP(std::string s); + + static inline bool ok(std::string w) + { + return (w == "OK"); + } + + void readSubFolders(std::string path, + mail::callback::folderList &callback1, + mail::callback &callback2); + + mail::addMessage *addMessage(std::string path, + mail::callback &callback); + + void folderStatus(std::string path, + mail::callback::folderInfo &callback1, + mail::callback &callback2); + + void openFolder(std::string path, mail::snapshot *restoreSnapshot, + mail::callback &openCallback, + mail::callback::folder &folderCallback); + + //////////// PUBLIC INTERFACE FUNCTIONS ////////////// + + // The constructors begins the login process. + + imap(std::string url, std::string passwd, + std::vector<std::string> &certificates, + mail::loginCallback *loginCallbackFunc, + mail::callback &callback, + mail::callback::disconnect &disconnectCallback); + + imap(const imap &); // UNDEFINED + imap &operator=(const imap &); // UNDEFINED + ~imap(); + + void resumed(); + + void handler(std::vector<pollfd> &fds, int &timeout); + // Event loop processing + + bool hasCapability(std::string capability); + std::string getCapability(std::string capability); + + std::string servertype; + std::string serverdescr; + + void logout(mail::callback &callback); + void checkNewMail(mail::callback &callback); + + std::string messageFlagStr(const mail::messageInfo &indexInfo, + mail::messageInfo *oldIndexInfo=NULL, + bool *flagsUpdated=NULL); + + std::vector <imapFolder> namespaces; + + std::vector <std::string> serverMsgs; // * NO, * OK ALERT... + +private: + + mail::folder *folderFromString(std::string); + void readTopLevelFolders(mail::callback::folderList &callback1, + mail::callback &callback2); + void findFolder(std::string folder, + class mail::callback::folderList &callback1, + class mail::callback &callback2); + std::string translatePath(std::string path); + + folder *getSendFolder(const smtpInfo &info, + const mail::folder *folder, + std::string &errmsg); + + size_t getFolderIndexSize(); + class mail::messageInfo getFolderIndexInfo(size_t); + void getFolderKeywordInfo(size_t, std::set<std::string> &); + +public: + // NOT public interface, for friends only: + + static void addSmapFolderCmd(mail::smapAddMessage *, std::string); + + void messagecmd(const std::vector<size_t> &messages, std::string parameters, + std::string operation, + std::string name, + mail::callback::message &callback); + // Message data retrieval function. + // messages - which messages to retrieve + // parameters - which attributes to retrieve. + // operation - the IMAP command name + // + // The typical command that the server received would be + // "name operation [msgset] parameters" (name is the imap tag). + + class msgSet; + + void messagecmd(msgSet &messages, std::string parameters, + std::string operation, + std::string name, + mail::callback::message &callback); + + // Like the above, except that 'messages' is an abstract superclass + // that's defined as follows: + + class msgSet { + public: + msgSet(); + virtual ~msgSet(); + + virtual bool getNextRange(size_t &first, size_t &last)=0; + // + // Each time getNextRange is invoked, it should initialize + // first/last to the starting/ending affected message number, + // and returns true; or false if no more messages + + }; + + // Construct a msgSet from UIDs of indicated messages. + // + // UIDs are used in order to handle situations where something + // got expunged in the middle of multiple fetches (yes, servers + // shouldn't really do this, but we can handle it). + + class msgSetRange : public msgSet { + + std::vector<size_t> msglist; + + std::vector<size_t>::iterator nextMsg; + + imap *p; + + static size_t uidNum(imap *, size_t); + + public: + msgSetRange(imap *pArg, const std::vector<size_t> &messages); + ~msgSetRange(); + + bool getNextRange(size_t &first, size_t &last); + }; + + +private: + + // mail::account implementations: + + void readMessageAttributes(const std::vector<size_t> &messages, + MessageAttributes attributes, + class mail::callback::message + &callback); + + void readMessageContent(const std::vector<size_t> &messages, + bool peek, + enum mail::readMode readType, + mail::callback::message &callback); + + void readMessageContent(size_t messageNum, + bool peek, + const mimestruct &msginfo, + enum mail::readMode readType, + mail::callback::message &callback); + + void readMessageContentDecoded(size_t messageNum, + bool peek, + const mimestruct &msginfo, + mail::callback::message &callback); + + void doReadMessageContent(const std::vector<size_t> &messages, + bool peek, + const class mimestruct *msginfo, + mail::readMode getType, + mail::callback::message &callback); + + void saveFolderIndexInfo(size_t messageNum, + const mail::messageInfo &indexInfo, + mail::callback &callback); + + void updateFolderIndexFlags(const std::vector<size_t> &messages, + bool doFlip, + bool enableDisable, + const mail::messageInfo &flags, + mail::callback &callback); + + void updateFolderIndexFlags(const std::vector<size_t> &messages, + bool enableDisable, + const mail::messageInfo &flags, + mail::callback &callback); + + void updateKeywords(const std::vector<size_t> &messages, + const std::set<std::string> &keywords, + bool setOrChange, + // false: set, true: see changeTo + bool changeTo, + callback &cb); + void updateImapKeywords(const std::vector<size_t> &messages, + const std::set<std::string> &keywords, + bool changeTo, + callback &cb); + class updateImapKeywordHelper; +public: + friend class updateImapKeywordHelper; +private: + + void updateNotify(bool enableDisable, callback &callbackArg); + + void updateFolderIndexInfo(mail::callback &callback); + void removeMessages(const std::vector<size_t> &messages, + callback &cb); + + void copyMessagesTo(const std::vector<size_t> &messages, + mail::folder *copyTo, + mail::callback &callback); + + void moveMessagesTo(const std::vector<size_t> &messages, + mail::folder *copyTo, + mail::callback &callback); + + void searchMessages(const class mail::searchParams &searchInfo, + class mail::searchCallback &callback); + +}; + +////////////////////////////////////////////////////////////////////////// +// +// A handler for a server response. Each time something is read from the +// server, all available handlers' process() method is invoked. A handler +// should return a non-zero count of consumed bytes from the readbuffer. +// A handler should return zero if readbuffer does not contain a response +// it understands. If imap finds a handler that consumes some read +// data, it is removed from the readbuffer, and imap tries again +// (in the event the server sent multiple responses). +// +// imap reports a fatal error if no handler claims newline-terminated +// data. +// +// A handler must wait until imap tells it that it can proceed to do +// whatever it needs to do. imap invokes the installed() method when +// the handler has the floor to itself. +// + +class imapHandler { + +protected: + imap *myimap; +public: + bool installedFlag; // true if this handler is active. + + bool isBackgroundTask; // true if this is a background handler + + int handlerTimeoutVal; // When this handler times out. + +protected: + time_t timeoutAt; // The actual system timestamp. + + public: + friend class imap; + + imapHandler(int timeoutValArg=0); + virtual ~imapHandler(); + + virtual void installed(imap &)=0; + virtual void anotherHandlerInstalled(mail::imap &); + + virtual bool getTimeout(imap &, int &); + virtual int getTimeout(imap &); + virtual void setTimeout(); + virtual void setTimeout(int timeoutVal); + virtual int process(imap &imapAccount, std::string &buffer)=0; + virtual const char *getName()=0; + + virtual void setBackgroundTask(bool flag) { isBackgroundTask=flag; } + virtual bool getBackgroundTask() const { return isBackgroundTask; } + + virtual void timedOut(const char *errmsg)=0; + +protected: + static void callbackTimedOut(mail::callback &callback, + const char *errmsg); +}; + +////////////////////////////////////////////////////////////////////////// +// +// A helper class that separates untagged and tagged replies. The class +// provides an implementation for process(). Subclasses should implement +// untaggedMessage() and/or taggedMessage(). +// + +class imapCommandHandler : public imapHandler { + +public: + imapCommandHandler(int timeout=0); + ~imapCommandHandler(); + +protected: + int process(imap &imapAccount, std::string &buffer); + +private: + // Callback: process an untagged message + // + // name: the name of the untagged reply (the first word after '*', + // uppercase). If return true, '* name' is removed from the input + // buffer, and the subclass is expected to have installed its own + // handler, now. + + virtual bool untaggedMessage(imap &imapAccount, std::string name)=0; + + // Callback: process a tagged message + // + // name: the tag + // + // message: the rest of the message + // + // okfail: true if the first word of message is status + // + // errmsg: everything following the first word of the message + // + // A true return removes the line from the input buffer + + virtual bool taggedMessage(imap &imapAccount, std::string name, + std::string message, + bool okfail, std::string errmsg)=0; + + // + // Continuation request received. + // + // A true return indicates that the continuation request has been + // processed. + // + + virtual bool continuationRequest(imap &imapAccount, + std::string request); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/imapacl.C b/libmail/imapacl.C new file mode 100644 index 0000000..c257bab --- /dev/null +++ b/libmail/imapacl.C @@ -0,0 +1,925 @@ +/* +** Copyright 2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "addmessage.H" +#include "imap.H" +#include "misc.H" +#include "imapacl.H" +#include "imapfolder.H" +#include "imapfolders.H" +#include "imaphandler.H" +#include "imaplisthandler.H" +#include "maildir/maildiraclt.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <sstream> +#include <algorithm> + +using namespace std; + + +LIBMAIL_START + +/* +** ACL1 MYRIGHTS response. +*/ + +class imapGetMyRights::parseMyRights : public imapHandlerStructured { + + string folder; + string &rights; + + string curfolder; + void (parseMyRights::*next_func)(imap &, Token); + +public: + parseMyRights(string folderArg, string &rightsArg); + ~parseMyRights(); + void installed(imap &imapAccount); + +private: + const char *getName(); + void timedOut(const char *); + void process(imap &imapAccount, Token t); + + void getFolder(imap &imapAccount, Token t); + void getRights(imap &imapAccount, Token t); + +}; + + +/* +** ACL2 LIST(MYRIGHTS) handler. +*/ + +class imapGetMyRights::listMyRights : private vector<imapFolder>, + public imapLIST { + + string folderName; + string &rights; + +public: + listMyRights(string folderNameArg, + string &rightsArg); + ~listMyRights(); + + void processExtendedAttributes(imapFolder &); +}; + +LIBMAIL_END + +mail::imapGetMyRights::imapGetMyRights(string folderNameArg, string &rightsArg, + mail::callback &callbackArg) + : folderName(folderNameArg), + rights(rightsArg), + callback(callbackArg) +{ +} + +mail::imapGetMyRights::~imapGetMyRights() +{ +} + +void mail::imapGetMyRights::installed(imap &imapAccount) +{ + if (imapAccount.getCapability("ACL") == "ACL") + { + /* ACL 1 */ + + imapAccount.imapcmd("MYRIGHTS", + "MYRIGHTS " + + imapAccount.quoteSimple(folderName) + + "\r\n"); + } + else + { + imapAccount.imapcmd("MYRIGHTS", + "LIST (MYRIGHTS) \"\" " + + imapAccount.quoteSimple(folderName) + + "\r\n"); + } +} + +const char *mail::imapGetMyRights::getName() +{ + return "MYRIGHTS"; +} + +void mail::imapGetMyRights::timedOut(const char *errmsg) +{ + callbackTimedOut(callback, errmsg); +} + +bool mail::imapGetMyRights::untaggedMessage(imap &imapAccount, + string msgname) +{ + if (msgname == "MYRIGHTS") + { + imapAccount + .installBackgroundTask(new parseMyRights(folderName, + rights)); + return true; + } + + if (msgname == "LIST") + { + imapAccount + .installBackgroundTask(new listMyRights(folderName, + rights)); + return true; + } + return false; +} + +bool mail::imapGetMyRights::taggedMessage(imap &imapAccount, string msgname, + string message, + bool okfail, string errmsg) +{ + if (!okfail) + { + callback.fail(errmsg); + imapAccount.uninstallHandler(this); + return true; + } + callback.success(message); + imapAccount.uninstallHandler(this); + return true; +} + +/* ---- Parse ACL1 MYRIGHTS response ----- */ + +mail::imapGetMyRights::parseMyRights::parseMyRights(string folderArg, + string &rightsArg) + : folder(folderArg), rights(rightsArg), + next_func(&mail::imapGetMyRights::parseMyRights::getFolder) +{ + rights=""; +} + +mail::imapGetMyRights::parseMyRights::~parseMyRights() +{ +} + +void mail::imapGetMyRights::parseMyRights::installed(imap &imapAccount) +{ +} + +const char *mail::imapGetMyRights::parseMyRights::getName() +{ + return "LISTMYRIGHTS_FOLDER"; +} + +void mail::imapGetMyRights::parseMyRights::timedOut(const char *dummy) +{ +} + +void mail::imapGetMyRights::parseMyRights::process(imap &imapAccount, Token t) +{ + (this->*next_func)(imapAccount, t); +} + +void mail::imapGetMyRights::parseMyRights::getFolder(imap &imapAcct, Token t) +{ + if (t != ATOM && t != STRING) + { + error(imapAcct); + return; + } + curfolder=t.text; + next_func=&mail::imapGetMyRights::parseMyRights::getRights; +} + +void mail::imapGetMyRights::parseMyRights::getRights(imap &imapAcct, Token t) +{ + if (t == EOL) + { + done(); + return; + } + + if (t != ATOM && t != STRING) + { + error(imapAcct); + return; + } + + string s=t.text; + + size_t p=s.find(ACL_DELETE_SPECIAL[0]); + + if (p != std::string::npos) + s.replace(s.begin()+p, s.begin()+p+1, + ACL_DELETEMSGS ACL_DELETEFOLDER ACL_EXPUNGE); + sort(s.begin(), s.end()); + + if (curfolder == folder) + rights=s; +} + +/* ---- Parse ACL2 LIST(MYRIGHTS) response ----- */ + +mail::imapGetMyRights::listMyRights::listMyRights(string folderNameArg, + string &rightsArg) + : imapLIST( *this, 0), folderName(folderNameArg), + rights(rightsArg) +{ +} + +mail::imapGetMyRights::listMyRights::~listMyRights() +{ +} + +void mail::imapGetMyRights::listMyRights +::processExtendedAttributes(imapFolder &f) +{ + if (f.getPath() != folderName) + return; /* Sanity check */ + + vector<mail::imapparsefmt *>::iterator b=xattributes.children.begin(), + e=xattributes.children.end(); + + while (b != e) + { + mail::imapparsefmt *node= *b++; + + if (node->children.size() < 2) + continue; + + string name=(*node->children.begin())->value; + + mail::upper(name); + + if (name != "MYRIGHTS") + continue; + + rights=node->children.begin()[1]->value; + + size_t p=rights.find(ACL_DELETE_SPECIAL[0]); + + if (p != std::string::npos) + rights.replace(rights.begin()+p, rights.begin()+p+1, + ACL_DELETEMSGS ACL_DELETEFOLDER ACL_EXPUNGE); + + sort(rights.begin(), rights.end()); + break; + } + +} + +/***** GET RIGHTS *****/ + +LIBMAIL_START + +/* +** ACL1 "ACL" response. +*/ + +class imapGetRights::parseGetRights : public imapHandlerStructured { + + string folder; + list<pair<string,string> > &rights; + + string curfolder; + string curidentifier; + void (parseGetRights::*next_func)(imap &, Token); + +public: + parseGetRights(string folderArg, list<pair<string,string> >&rightsArg); + ~parseGetRights(); + void installed(imap &imapAccount); + +private: + const char *getName(); + void timedOut(const char *); + void process(imap &imapAccount, Token t); + + void getFolder(imap &imapAccount, Token t); + void getIdentifier(imap &imapAccount, Token t); + void getRights(imap &imapAccount, Token t); + +}; + +LIBMAIL_END + +/* +** ACL2 LIST(ACL) handler. +*/ + +class mail::imapGetRights::listGetRights : private vector<mail::imapFolder>, + public mail::imapLIST { + + string folderName; + list< pair< string, string> > &rights; + +public: + listGetRights(string folderNameArg, + list<pair< string, string> > &rightsArg); + ~listGetRights(); + + void processExtendedAttributes(mail::imapFolder &); +}; + +mail::imapGetRights::imapGetRights(string folderNameArg, + list<pair<string,string> >&rightsArg, + mail::callback &callbackArg) + : folderName(folderNameArg), rights(rightsArg), + callback(callbackArg) +{ +} + +mail::imapGetRights::~imapGetRights() +{ +} + +void mail::imapGetRights::installed(imap &imapAccount) +{ + if (imapAccount.getCapability("ACL") == "ACL") + { + /* ACL 1 */ + + imapAccount.imapcmd("GETACL", + "GETACL " + + imapAccount.quoteSimple(folderName) + + "\r\n"); + } + else + { + imapAccount.imapcmd("GETACL", + "LIST (ACL) \"\" " + + imapAccount.quoteSimple(folderName) + + "\r\n"); + } + +} + +const char *mail::imapGetRights::getName() +{ + return "GETACL"; +} + +void mail::imapGetRights::timedOut(const char *errmsg) +{ + callbackTimedOut(callback, errmsg); +} + +bool mail::imapGetRights::untaggedMessage(imap &imapAccount, string msgname) +{ + if (msgname == "LIST") + { + imapAccount + .installBackgroundTask(new listGetRights(folderName, + rights)); + return true; + } + + if (msgname == "ACL") + { + imapAccount + .installBackgroundTask(new parseGetRights(folderName, + rights)); + return true; + } + return false; +} + +bool mail::imapGetRights::taggedMessage(imap &imapAccount, string msgname, + string message, + bool okfail, string errmsg) +{ + if (!okfail) + { + callback.fail(errmsg); + imapAccount.uninstallHandler(this); + return true; + } + callback.success(message); + imapAccount.uninstallHandler(this); + return true; +} + +/* --- Process ACL1 "ACL" response --- */ + +mail::imapGetRights::parseGetRights::parseGetRights(string folderArg, + list<pair<string,string> > + &rightsArg) + : folder(folderArg), rights(rightsArg), + next_func(&mail::imapGetRights::parseGetRights::getFolder) +{ + rights.clear(); +} + +mail::imapGetRights::parseGetRights::~parseGetRights() +{ +} + +void mail::imapGetRights::parseGetRights::installed(imap &imapAccount) +{ +} + +const char *mail::imapGetRights::parseGetRights::getName() +{ + return "GETACL_FOLDER"; +} + +void mail::imapGetRights::parseGetRights::timedOut(const char *dummy) +{ +} + +void mail::imapGetRights::parseGetRights::process(imap &imapAccount, Token t) +{ + (this->*next_func)(imapAccount, t); +} + +void mail::imapGetRights::parseGetRights::getFolder(imap &imapAcct, Token t) +{ + if (t != ATOM && t != STRING) + { + error(imapAcct); + return; + } + curfolder=t.text; + next_func=&mail::imapGetRights::parseGetRights::getIdentifier; +} + +void mail::imapGetRights::parseGetRights::getIdentifier(imap &imapAcct,Token t) +{ + if (t == EOL) + { + done(); + return; + } + + if (t != ATOM && t != STRING) + { + error(imapAcct); + return; + } + + curidentifier=t.text; + + /* Fudge ACL 1 identifiers into ACL 2 identifiers */ + + if (curidentifier != "anyone" && + curidentifier != "anonymous" && + curidentifier != "authuser" && + curidentifier != "owner" && + curidentifier != "administrators") + curidentifier="user=" + curidentifier; + + next_func=&mail::imapGetRights::parseGetRights::getRights; +} + +void mail::imapGetRights::parseGetRights::getRights(imap &imapAcct, Token t) +{ + if (t == EOL) + { + done(); + return; + } + + if (t != ATOM && t != STRING) + { + error(imapAcct); + return; + } + + string s=t.text; + + size_t p=s.find(ACL_DELETE_SPECIAL[0]); + + if (p != std::string::npos) + s.replace(s.begin()+p, s.begin()+p+1, + ACL_DELETEMSGS ACL_DELETEFOLDER ACL_EXPUNGE); + sort(s.begin(), s.end()); + + if (curfolder == folder) + rights.push_back(make_pair(curidentifier, s)); + next_func=&mail::imapGetRights::parseGetRights::getIdentifier; +} + +/* --- Process ACL2 LIST(ACL) response --- */ + +mail::imapGetRights +::listGetRights::listGetRights(string folderNameArg, + list<pair<string,string> > &rightsArg) + : imapLIST(*this, 0), folderName(folderNameArg), + rights(rightsArg) +{ +} + +mail::imapGetRights::listGetRights::~listGetRights() +{ +} + +void mail::imapGetRights::listGetRights +::processExtendedAttributes(imapFolder &f) +{ + if (f.getPath() != folderName) + return; /* Sanity check */ + + vector<mail::imapparsefmt *>::iterator b=xattributes.children.begin(), + e=xattributes.children.end(); + + while (b != e) + { + mail::imapparsefmt *node= *b++; + + if (node->children.size() < 2) + continue; + + string name=(*node->children.begin())->value; + + mail::upper(name); + + if (name != "ACL") + continue; + + mail::imapparsefmt *rights_list=node->children.begin()[1]; + + vector<mail::imapparsefmt *>::iterator + bb=rights_list->children.begin(), + ee=rights_list->children.end(); + + while (bb != ee) + { + mail::imapparsefmt *node= *bb++; + + if (node->children.size() < 2) + continue; + + string identifier=node->children.begin()[0]->value; + string r=node->children.begin()[1]->value; + + size_t p=r.find(ACL_DELETE_SPECIAL[0]); + + if (p != std::string::npos) + r.replace(r.begin()+p, r.begin()+p+1, + ACL_DELETEMSGS ACL_DELETEFOLDER + ACL_EXPUNGE); + + sort(r.begin(), r.end()); + rights.push_back(make_pair(identifier, r)); + } + break; + } +} + +/* --- DELETE or SET ACL rights */ + + +LIBMAIL_START + +/* ACL2 RIGHTS-INFO and ACLFAILED untagged messages */ + +class imapSetRights::parseRightsInfo : public imapHandlerStructured { + + string &folderName; + string &identifier; + vector<string> &rights; + + void (parseRightsInfo::*next_func)(imap &, Token); + +public: + parseRightsInfo(string &folderNameArg, + string &identifierArg, + vector<string> &rightsArg); + ~parseRightsInfo(); + + void installed(imap &imapAccount); + const char *getName(); + void timedOut(const char *); + void process(imap &imapAccount, Token t); + + void get_folder(imap &imapAccount, Token t); + void get_identifier(imap &imapAccount, Token t); + void get_rights(imap &imapAccount, Token t); + +}; + +class imapSetRights::parseAclFailed : public imapHandlerStructured { + + string &folderName, &errmsg; + bool wantErrorMessage; + void (parseAclFailed::*next_func)(imap &, Token); +public: + parseAclFailed(string &folderNameArg, string &errmsgArg); + ~parseAclFailed(); + + void installed(imap &imapAccount); + const char *getName(); + void timedOut(const char *); + void process(imap &imapAccount, Token t); + +private: + int process(imap &imapAccount, std::string &buffer); +}; +LIBMAIL_END + +mail::imapSetRights::imapSetRights(string folderNameArg, + string identifierArg, + string rightsArg, + bool delIdentifierArg, + string &errorIdentifierArg, + vector<string> &errorRightsArg, + mail::callback &callbackArg) + : folderName(folderNameArg), + identifier(identifierArg), + rights(rightsArg), + delIdentifier(delIdentifierArg), + errorIdentifier(errorIdentifierArg), + errorRights(errorRightsArg), + callback(callbackArg) +{ +} + +mail::imapSetRights::~imapSetRights() +{ +} + +void mail::imapSetRights::installed(imap &imapAccount) +{ + if (imapAccount.getCapability("ACL") == "ACL") + { + /* ACL 1 */ + + string n=identifier; + + if (n != "anyone" && + n != "anonymous" && + n != "authuser" && + n != "owner" && + n != "administrators") + { + if (strncmp(n.c_str(), "user=", 5)) + { + callback.fail("Invalid ACL identifier."); + imapAccount.uninstallHandler(this); + return; + } + + n=n.substr(5); + } + + // If all three "etx" were originally set, it would've been + // replaced with a "d". If we still have one or more of them + // it means that not all three were specified. + + if (rights.find(ACL_DELETEMSGS[0]) != std::string::npos || + rights.find(ACL_DELETEFOLDER[0]) != std::string::npos || + rights.find(ACL_EXPUNGE[0]) != std::string::npos) + { + errorIdentifier=identifier; + errorRights.push_back(""); + errorRights.push_back(ACL_DELETEMSGS + ACL_EXPUNGE + ACL_DELETEFOLDER); + errorRights.push_back(ACL_LOOKUP); + errorRights.push_back(ACL_READ); + errorRights.push_back(ACL_SEEN); + errorRights.push_back(ACL_WRITE); + errorRights.push_back(ACL_INSERT); + errorRights.push_back(ACL_POST); + errorRights.push_back(ACL_CREATE); + errorRights.push_back(ACL_ADMINISTER); + callback.fail("Invalid access right combination."); + imapAccount.uninstallHandler(this); + return; + } + + imapAccount.imapcmd("SETACL", + delIdentifier ? + "DELETEACL " + + imapAccount.quoteSimple(folderName) + + " " + + imapAccount.quoteSimple(n) + + "\r\n" + : + "SETACL " + + imapAccount.quoteSimple(folderName) + + " " + + imapAccount.quoteSimple(n) + + " " + + imapAccount.quoteSimple(rights) + + "\r\n"); + } + else + { + imapAccount.imapcmd("SETACL", + delIdentifier ? + "ACL DELETE " + + imapAccount.quoteSimple(folderName) + + " " + + imapAccount.quoteSimple(identifier) + + "\r\n" + : + "ACL STORE " + + imapAccount.quoteSimple(folderName) + + " " + + imapAccount.quoteSimple(identifier) + + " " + + imapAccount.quoteSimple(rights) + + "\r\n"); + } +} + +const char *mail::imapSetRights::getName() +{ + return "SETACL"; +} + +void mail::imapSetRights::timedOut(const char *errmsg) +{ + callbackTimedOut(callback, errmsg); +} + +bool mail::imapSetRights::untaggedMessage(imap &imapAccount, string msgname) +{ + if (msgname == "LIST") + { + dummy_rights.clear(); + imapAccount + .installBackgroundTask(new imapGetRights + ::listGetRights(folderName, + dummy_rights)); + return true; + } + + if (msgname == "ACLFAILED") + { + imapAccount + .installBackgroundTask(new parseAclFailed(dummy, + dummy)); + return true; + } + + if (msgname == "RIGHTS-INFO") + { + imapAccount + .installBackgroundTask(new + parseRightsInfo(dummy, + errorIdentifier, + errorRights)); + return true; + } + + return false; +} + +bool mail::imapSetRights::taggedMessage(imap &imapAccount, string msgname, + string message, + bool okfail, string errmsg) +{ + if (okfail && errorIdentifier.size() > 0) + { + okfail=false; + message="Invalid access rights."; + } + + if (!okfail) + { + callback.fail(errmsg); + imapAccount.uninstallHandler(this); + return true; + } + callback.success(message); + imapAccount.uninstallHandler(this); + return true; +} + + +mail::imapSetRights::parseRightsInfo::parseRightsInfo(string &folderNameArg, + string &identifierArg, + vector<string> &rightsArg) + : folderName(folderNameArg), + identifier(identifierArg), + rights(rightsArg), next_func(&mail::imapSetRights::parseRightsInfo + ::get_folder) +{ +} + +mail::imapSetRights::parseRightsInfo::~parseRightsInfo() +{ +} + +void mail::imapSetRights::parseRightsInfo::installed(imap &imapAccount) +{ +} + +const char *mail::imapSetRights::parseRightsInfo::getName() +{ + return "RIGHTS-INFO"; +} + +void mail::imapSetRights::parseRightsInfo::timedOut(const char *dummy) +{ +} + +void mail::imapSetRights::parseRightsInfo::process(imap &imapAccount, Token t) +{ + (this->*next_func)(imapAccount, t); +} + +void mail::imapSetRights::parseRightsInfo::get_folder(imap &imapAccount, + Token t) +{ + if (t != ATOM && t != STRING) + { + error(imapAccount); + return; + } + folderName=t.text; + next_func= &mail::imapSetRights::parseRightsInfo::get_identifier; +} + +void mail::imapSetRights::parseRightsInfo::get_identifier(imap &imapAccount, + Token t) +{ + if (t != ATOM && t != STRING) + { + error(imapAccount); + return; + } + identifier=t.text; + next_func= &mail::imapSetRights::parseRightsInfo::get_rights; +} + +void mail::imapSetRights::parseRightsInfo::get_rights(imap &imapAccount, + Token t) +{ + if (t == EOL) + { + done(); + return; + } + if (t != ATOM && t != STRING) + { + error(imapAccount); + return; + } + rights.push_back(t.text); +} + +mail::imapSetRights::parseAclFailed::parseAclFailed(string &folderNameArg, + string &errmsgArg) + : folderName(folderNameArg), + errmsg(errmsgArg), + wantErrorMessage(false) +{ +} + +mail::imapSetRights::parseAclFailed::~parseAclFailed() +{ +} + +void mail::imapSetRights::parseAclFailed::installed(imap &imapAccount) +{ +} + +const char *mail::imapSetRights::parseAclFailed::getName() +{ + return "ACLFAILED"; +} + +void mail::imapSetRights::parseAclFailed::timedOut(const char *dummy) +{ +} + +void mail::imapSetRights::parseAclFailed::process(imap &imapAccount, + Token t) +{ + if (t != ATOM && t != STRING) + { + error(imapAccount); + return; + } + + folderName=t.text; + wantErrorMessage=true; +} + +int mail::imapSetRights::parseAclFailed::process(imap &imapAccount, + string &buffer) +{ + if (!wantErrorMessage) + return imapHandlerStructured::process(imapAccount, buffer); + + size_t p=buffer.find('\n'); + + if (p == std::string::npos) + return 0; + + string msg=buffer.substr(0, p); + + size_t cr=msg.find('\r'); + if (cr != std::string::npos) + msg=msg.substr(0, cr); + errmsg=msg; + done(); + return (int)p; +} diff --git a/libmail/imapacl.H b/libmail/imapacl.H new file mode 100644 index 0000000..c784b09 --- /dev/null +++ b/libmail/imapacl.H @@ -0,0 +1,115 @@ +/* +** Copyright 2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_imapacl_H +#define libmail_imapacl_H + +#include "libmail_config.h" +#include "mail.H" +#include "imap.H" + +LIBMAIL_START + +class imapGetMyRights : public imapCommandHandler { + + std::string folderName; + std::string &rights; + mail::callback &callback; + +public: + imapGetMyRights(std::string folderName, std::string &rightsArg, + mail::callback &callbackArg); + ~imapGetMyRights(); + + void installed(imap &imapAccount); + +private: + const char *getName(); + void timedOut(const char *errmsg); + + bool untaggedMessage(imap &imapAccount, std::string msgname); + + bool taggedMessage(imap &imapAccount, std::string msgname, + std::string message, + bool okfail, std::string errmsg); + + class parseMyRights; + class listMyRights; +}; + +class imapGetRights : public imapCommandHandler { + + std::string folderName; + std::list<std::pair<std::string, std::string> > &rights; + + mail::callback &callback; + +public: + imapGetRights(std::string folderName, + std::list<std::pair<std::string,std::string> >&rightsArg, + mail::callback &callbackArg); + ~imapGetRights(); + + void installed(imap &imapAccount); + +private: + const char *getName(); + void timedOut(const char *errmsg); + + bool untaggedMessage(imap &imapAccount, std::string msgname); + + bool taggedMessage(imap &imapAccount, std::string msgname, + std::string message, + bool okfail, std::string errmsg); + +public: + class parseGetRights; + class listGetRights; +}; + +class imapSetRights : public imapCommandHandler { + + std::string folderName; + std::string identifier; + std::string rights; + bool delIdentifier; + std::string &errorIdentifier; + std::vector<std::string> &errorRights; + + mail::callback &callback; + + std::string dummy; + + std::list< std::pair< std::string, std::string> > dummy_rights; + +public: + imapSetRights(std::string folderName, + std::string identifierArg, + std::string rightsArg, + bool delIdentifierArg, + std::string &errorIdentifier, + std::vector<std::string> &errorRights, + mail::callback &callbackArg); + ~imapSetRights(); + + void installed(imap &imapAccount); + +private: + const char *getName(); + void timedOut(const char *errmsg); + + bool untaggedMessage(imap &imapAccount, std::string msgname); + + bool taggedMessage(imap &imapAccount, std::string msgname, + std::string message, + bool okfail, std::string errmsg); + + class parseRightsInfo; + class parseAclFailed; +}; + +LIBMAIL_END + +#endif diff --git a/libmail/imapfetchhandler.C b/libmail/imapfetchhandler.C new file mode 100644 index 0000000..20701fe --- /dev/null +++ b/libmail/imapfetchhandler.C @@ -0,0 +1,147 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "imapfetchhandler.H" + +using namespace std; + +mail::imapFetchHandler +::imapFetchHandler(mail::callback::message &callbackArg, + string nameArg) + : callback(callbackArg), + name(nameArg), + imapAccount(NULL), + counter(0), + messageTextEstimatedSize(0), + messageTextCompleted(0), + messageCntDone(0), + messageCntTotal(0) +{ +} + +mail::imapFetchHandler::~imapFetchHandler() +{ + if (imapAccount) + imapAccount->currentFetch=NULL; +} + +bool mail::imapFetchHandler::untaggedMessage(mail::imap &imapAccount, string name) +{ + return false; +} + +bool mail::imapFetchHandler::taggedMessage(mail::imap &imapAccount, string name, + string message, + bool okfail, string errmsg) +{ + if (name == getName()) + { + if (!okfail) + { + if (failmsg.size() == 0) + failmsg=errmsg; + } + + // Long message sets are broken up. We need to count the + // tagged replies received, and only report smashing success + // (or flaming failure) when the last one comes in. + + --counter; + + if (counter == 0) + { + if (failmsg.size() > 0) + callback.fail(failmsg); + else + callback.success(errmsg); + imapAccount.uninstallHandler(this); + } + return true; + } + return false; +} + +void mail::imapFetchHandler::installed(mail::imap &imapArg) +{ + imapAccount= &imapArg; + + if (imapAccount->currentFetch) + imapAccount->currentFetch->imapAccount=NULL; // We're the top dog now. + + imapAccount->currentFetch=this; + + // Just spit out all the commands, all together. + + while (!commands.empty()) + { + pair<string, string> cmd=commands.front(); + + commands.pop(); + imapAccount->imapcmd(cmd.first, cmd.second); + } +} + +// As stuff comes back from the server, make sure to bump the timeout +// to let mail::imap know we're still alive. + +void mail::imapFetchHandler::messageTextCallback(size_t messageNum, + string text) +{ + setTimeout(); + callback.reportProgress(messageTextCompleted, + messageTextEstimatedSize, + messageCntDone, messageCntTotal); + callback.messageTextCallback(messageNum, text); +} + +void mail::imapFetchHandler::messageEnvelopeCallback(size_t messageNumber, + const mail::envelope + &envelope) +{ + setTimeout(); + callback.messageEnvelopeCallback(messageNumber, envelope); +} + +void mail::imapFetchHandler::messageReferencesCallback(size_t messageNumber, + const vector<string> + &references) +{ + setTimeout(); + callback.messageReferencesCallback(messageNumber, references); +} + + +void mail::imapFetchHandler::messageStructureCallback(size_t messageNumber, + const mail::mimestruct & + messageStructure) +{ + setTimeout(); + callback.messageStructureCallback(messageNumber, messageStructure); +} + + +void mail::imapFetchHandler::messageArrivalDateCallback(size_t messageNumber, + time_t datetime) +{ + setTimeout(); + callback.messageArrivalDateCallback(messageNumber, datetime); +} + +void mail::imapFetchHandler::messageSizeCallback(size_t messageNumber, + unsigned long messagesize) +{ + setTimeout(); + callback.messageSizeCallback(messageNumber, messagesize); +} + +void mail::imapFetchHandler::timedOut(const char *errmsg) +{ + callbackTimedOut(callback, errmsg); +} + +const char *mail::imapFetchHandler::getName() +{ + return name.c_str(); +} diff --git a/libmail/imapfetchhandler.H b/libmail/imapfetchhandler.H new file mode 100644 index 0000000..dcc23f6 --- /dev/null +++ b/libmail/imapfetchhandler.H @@ -0,0 +1,74 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_imapfetchhandler_H +#define libmail_imapfetchhandler_H + +#include "config.h" +#include "imap.H" +#include "imaphandler.H" + +LIBMAIL_START + +/////////////////////////////////////////////////////////////////////////// +// +// A helper class for processing IMAP FETCH. + +class imapFetchHandler : public imapCommandHandler { + + mail::callback::message &callback; + + std::string name; + + imap *imapAccount; + + std::string failmsg; + +public: + int counter; + + std::queue<std::pair<std::string, std::string> > commands; + + imapFetchHandler(mail::callback::message &callbackArg, + std::string nameArg); + + ~imapFetchHandler(); + + bool untaggedMessage(imap &imapAccount, std::string name); + + bool taggedMessage(imap &imapAccount, std::string name, + std::string message, + bool okfail, std::string errmsg); + + void installed(imap &imapArg); + + void messageTextCallback(size_t messageNum, std::string text); + void messageEnvelopeCallback(size_t messageNumber, + const envelope &envelope); + void messageReferencesCallback(size_t messageNumber, + const std::vector<std::string> + &references); + void messageStructureCallback(size_t messageNumber, + const mimestruct &messageStructure); + + void messageArrivalDateCallback(size_t messageNumber, + time_t datetime); + + void messageSizeCallback(size_t messageNumber, + unsigned long messagesize); + + void timedOut(const char *); + + size_t messageTextEstimatedSize; + size_t messageTextCompleted; + size_t messageCntDone; + size_t messageCntTotal; +private: + const char *getName(); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/imapfolder.C b/libmail/imapfolder.C new file mode 100644 index 0000000..7f00f35 --- /dev/null +++ b/libmail/imapfolder.C @@ -0,0 +1,3498 @@ +/* +** 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 <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <sstream> +#include <limits.h> +#include <errno.h> + +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; n<f->index.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<std::string> &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<string> 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<imapFOLDERinfo::indexInfo> &indexRef= + imapAccount.currentFolder->index; + + indexRef.erase(indexRef.begin() + c, + indexRef.begin() + c + 1); + + if (c < imapAccount.currentFolder->exists) + { + --imapAccount.currentFolder->exists; + + vector< pair<size_t, size_t> > 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; n<flags.size(); n++) + { + string f=flags[n]; + + upper(f); + if (f == "\\DRAFT") + i.draft=1; + else if (f == "\\ANSWERED") + i.replied=1; + else if (f == "\\FLAGGED") + i.marked=1; + else if (f == "\\DELETED") + i.deleted=1; + else if (f == "\\SEEN") + i.unread=0; + else if (f == "\\RECENT") + i.recent=1; + else if (*flags[n].c_str() != '\\') + { + if (!newMessage.addFlag(imapAccount + .keywordHashtable, + flags[n])) + LIBMAIL_THROW(strerror(errno)); + } + } + + i.keywords=newMessage; + + if (count <= imapAccount.currentFolder->exists) + imapAccount.currentFolder-> + folderCallback.messageChanged(count-1); + } +} + +static void parse_addr_list(mail::imapparsefmt *list, + vector<mail::address> &addresses) +{ + vector<mail::imapparsefmt *>::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<mail::imapparsefmt *>::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<mail::imapparsefmt *>::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<mail::imapparsefmt *>::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<mail::imapparsefmt *>::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> address_list; + size_t err_index; + + address::fromString(references_buf, + address_list, + err_index); + + vector<string> msgid_list; + + vector<address>::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<string> oflags; + currentFolder->index[messageNum].keywords.getFlags(oflags); + + set<string>::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<size_t> &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<size_t> enabled, disabled;\ +\ + vector<size_t>::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<size_t> &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<mail::imap> myimap; + +public: + set<string> newflags; + set<string> 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<size_t> &messages, + const set<string> &keywords, + bool setOrChange, + // false: set, true: see changeTo + bool changeTo, + callback &cb) +{ + set<string>::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<string, string> allFlags; + // Keyed by upper(name), value=name + // (be nice, and preserve casing when turning off + // flags). + + vector<size_t>::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<string> tempSet; + + currentFolder->index[n].keywords + .getFlags(tempSet); + + set<string>::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<string> allFlagsV; + + map<string, string>::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<size_t> msgSet; + + mail::imapFOLDERinfo *f=myimap.isDestroyed() ? NULL: + myimap->currentFolder; + + size_t n=f ? f->index.size():0; + size_t i; + + for (i=0; i<n; i++) + if (uids.count(f->index[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<size_t> &messages, + const set<string> &keywords, + bool changeTo, + callback &cb) +{ + set<string>::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<size_t>::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<size_t>::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<size_t> &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<size_t> &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<size_t> found; + + class SearchResults : public imapHandlerStructured { + vector<size_t> &found; + public: + SearchResults(vector<size_t> &); + ~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 <size_t> &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<string> uids; + + ptr<mail::imap> acct; + mail::callback &callback; + + bool uidSent; + +public: + + imapUIDEXPUNGE(mail::imap &imapAccount, + mail::callback &callbackArg, + const vector<size_t> &msgs); + ~imapUIDEXPUNGE(); + + void mkVector(mail::imap &, vector<size_t> &); + +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<size_t> &msgs) + : acct(&imapAccount), callback(callbackArg), uidSent(false) +{ + vector<size_t>::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<size_t> &vec) +{ + if (imapAccount.currentFolder) + { + size_t i; + + for (i=0; i<imapAccount.currentFolder->exists; 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<size_t> 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<size_t> &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<size_t> 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; + } +} diff --git a/libmail/imapfolder.H b/libmail/imapfolder.H new file mode 100644 index 0000000..4851fd2 --- /dev/null +++ b/libmail/imapfolder.H @@ -0,0 +1,140 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_imapfolder_H +#define libmail_imapfolder_H + +#include "libmail_config.h" +#include "maildir/maildirkeywords.h" +#include "mail.H" +#include "imap.H" +#include <vector> + +LIBMAIL_START + +class imap; + +class imapFOLDERinfo { + +public: + mail::callback::folder &folderCallback; + + imapFOLDERinfo(std::string pathArg, + mail::callback::folder &folderCallbackArg); + virtual ~imapFOLDERinfo(); + + class indexInfo : public mail::messageInfo { + public: + mail::keywords::Message keywords; + + indexInfo(); + ~indexInfo(); + }; + + std::vector<indexInfo> index; + size_t exists; + // May be less than index.length(), when + // synchronization is in progress + + bool closeInProgress; + + std::string path; + + virtual void opened(); // Used by SMAP + virtual void existsMore(mail::imap &, size_t)=0; + virtual void resetMailCheckTimer()=0; + virtual void setUid(size_t, std::string); +}; + + +///////////////////////////////////////////////////////////////////////// +// +// A currently-SELECTED folder. +// +// This object handles all kinds of messages from the IMAP server when a +// folder is open. +// + +class imapFOLDER : public imapCommandHandler, public imapFOLDERinfo { + + int mailCheckInterval; + +public: + + class existsCallback : public mail::callback { + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); // TODO + + public: + existsCallback(); + ~existsCallback(); + class imapFOLDER *me; + + private: + void success(std::string message); + void fail(std::string message); + } *existsNotify; + + std::string uidv; + + imapFOLDER(std::string pathArg, std::string uidvArg, size_t existsArg, + mail::callback::folder &folderCallback, + mail::imap &myserver); + + ~imapFOLDER(); + + static const char name[]; + + void installed(imap &imapAccount); +private: + const char *getName(); + void timedOut(const char *); + virtual int getTimeout(imap &); + + bool untaggedMessage(imap &imapAccount, std::string name); + bool taggedMessage(imap &imapAccount, std::string name, + std::string message, + bool okfail, std::string errmsg); + + void existsMore(mail::imap &, size_t); + void resetMailCheckTimer(); + void setUid(size_t, std::string); +}; + +// The SMAP version + +class smapFOLDER : public imapFOLDERinfo, public imapHandler { + + int mailCheckInterval; + + void installed(imap &); + int process(imap &imapAccount, std::string &buffer); + const char *getName(); + void timedOut(const char *errmsg); + + int getTimeout(mail::imap &imapAccount); + + bool openedFlag; + +public: + static const char name[]; + + smapFOLDER(std::string pathArg, + mail::callback::folder &folderCallbackArg, + mail::imap &myserver); + ~smapFOLDER(); + + void existsMore(mail::imap &imapAccount, size_t n); + void resetMailCheckTimer(); + void opened(); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/imapfolders.C b/libmail/imapfolders.C new file mode 100644 index 0000000..2a3fab5 --- /dev/null +++ b/libmail/imapfolders.C @@ -0,0 +1,1637 @@ +/* +** Copyright 2002-2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "addmessage.H" +#include "imap.H" +#include "misc.H" +#include "imapacl.H" +#include "imapfolder.H" +#include "imapfolders.H" +#include "imaplisthandler.H" + +#include "smapacl.H" +#include "smapcreate.H" +#include "smapdelete.H" +#include "smapaddmessage.H" +#include "smapsendfolder.H" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <sstream> +#include "unicode/unicode.h" +#include "rfc822/rfc822.h" +#include "maildir/maildiraclt.h" + +using namespace std; + +// +// Top level folders -- this stuff is taken from the IMAP NAMESPACE messages +// at login. + +void mail::imap::readTopLevelFolders(mail::callback::folderList &callback1, + mail::callback &callback2) +{ + vector<const mail::folder *> folders; + vector<mail::imapFolder>::iterator b, e; + + b=namespaces.begin(); + e=namespaces.end(); + + while (b != e) + { + folders.push_back(&*b); + b++; + } + + callback1.success(folders); + callback2.success("OK"); +} + +mail::folder *mail::imap::folderFromString(string s) +{ + string words[4]; + + words[0]=""; + words[1]=""; + words[2]=""; + words[3]=""; + int i=0; + + string::iterator b=s.begin(), e=s.end(); + + while (b != e) + { + if (*b == ':') + { + b++; + ++i; + continue; + } + + if (*b == '\\') + { + b++; + if (b == e) + break; + } + + if (i < 4) + words[i].append(&*b, 1); + b++; + } + + mail::imapFolder *f=new mail::imapFolder(*this, + words[0], + words[1], + words[2], + -1); + + if (f == NULL) + return NULL; + + f->hasMessages(words[3].find('S') != std::string::npos); + f->hasSubFolders(words[3].find('C') != std::string::npos); + + return f; +} + +mail::imapFolder::imapFolder(mail::imap &myImap, string pathArg, + string hierArg, string nameArg, + int typeArg) + : mail::folder(&myImap), + imapAccount(myImap), path(pathArg), hiersep(hierArg), name(nameArg), + hasmessages(true), + hassubfolders(false), type(typeArg) +{ +} + +mail::imapFolder::imapFolder(const mail::imapFolder &c) + : mail::folder(&c.imapAccount), imapAccount(c.imapAccount) +{ + (*this)=c; +} + +mail::imapFolder &mail::imapFolder::operator=(const mail::imapFolder &c) +{ + mail::folder::operator=(c); + path=c.path; + hiersep=c.hiersep; + name=c.name; + + hasmessages=c.hasmessages; + hassubfolders=c.hassubfolders; + type=c.type; + return *this; +} + +mail::imapFolder::~imapFolder() +{ +} + +void mail::imapFolder::hasMessages(bool flag) +{ + hasmessages=flag; +} + +void mail::imapFolder::hasSubFolders(bool flag) +{ + hassubfolders=flag; +} + +string mail::imapFolder::getName() const +{ + return name; +} + +string mail::imapFolder::getPath() const +{ + return path; +} + +bool mail::imapFolder::isParentOf(string cpath) const +{ + if (isDestroyed()) + return false; + + string pfix=path + hiersep; + + if (imapAccount.smap) + pfix += "/"; // SMAP implies this + + return (cpath.size() > pfix.size() && + pfix == cpath.substr(0, pfix.size())); +} + +bool mail::imapFolder::hasMessages() const +{ + return hasmessages; +} + +bool mail::imapFolder::hasSubFolders() const +{ + return hassubfolders; +} + +void mail::imapFolder::getParentFolder(callback::folderList &callback1, + callback &callback2) const +{ + if (isDestroyed(callback2)) + return; + + string sep=hiersep; + + if (imapAccount.smap) + sep="/"; // SMAP implied + + if (sep.size() > 0) + { + size_t n=path.rfind(sep[0]); + + if (n != std::string::npos) + { + mail::account &a=imapAccount; + + a.findFolder(path.substr(0, n), + callback1, callback2); + return; + } + } + + mail::imapFolder topFolder( *this ); + + topFolder.path=""; + topFolder.hiersep=""; + topFolder.hasmessages=false; + topFolder.hassubfolders=true; + topFolder.name=""; + + vector<const mail::folder *> array; + + array.push_back(&topFolder); + + callback1.success(array); + callback2.success("OK"); +} + +void mail::imapFolder::readSubFolders(mail::callback::folderList &callback1, + mail::callback &callback2) + const +{ + if (isDestroyed(callback2)) + return; + + imapAccount.readSubFolders(path + hiersep, callback1, callback2); +} + +mail::addMessage *mail::imapFolder::addMessage(mail::callback &callback) const +{ + if (isDestroyed(callback)) + return NULL; + + return imapAccount.addMessage(path, callback); +} + +void mail::imapFolder::readFolderInfo(mail::callback::folderInfo &callback1, + mail::callback &callback2) const +{ + if (isDestroyed(callback2)) + return; + + if (imapAccount.currentFolder && + imapAccount.currentFolder->path == path) + { + imapFOLDERinfo *f=imapAccount.currentFolder; + + vector<imapFOLDERinfo::indexInfo>::iterator b, e; + + b=f->index.begin(); + e=f->index.end(); + + callback1.messageCount=f->index.size(); + callback1.unreadCount=0; + + while (b != e) + { + if (b->unread) + ++callback1.unreadCount; + b++; + } + callback1.success(); + callback2.success("OK"); + return; + } + + if (!imapAccount.smap && callback1.fastInfo && + !imapAccount.hasCapability(LIBMAIL_CHEAPSTATUS)) + { + callback2.success("OK"); + return; + } + + imapAccount.folderStatus(path, callback1, callback2); +} + +////////////////////////////////////////////////////////////////////////////// +// +// CREATE a subfolder of an existing folder. First, issue a LIST to +// determine the eventual hierarchy separator character, followed by a CREATE. +// +// imapCREATE is also commandeered to rename a folder, since the processing +// is rather similar. + +LIBMAIL_START + +class imapCREATE : public imapCommandHandler { + + string renamePath; // If not empty, this is a rename + string parentPath; + string name; + string encodedname; + bool hasMessages; + bool hasSubfolders; + mail::callback::folderList &callback1; + mail::callback &callback2; + + bool listHierSent; + vector <imapFolder> hiersep; // Results of LIST for the hier sep + +public: + imapCREATE(string parentPathArg, // path + hiersep + string nameArg, + bool hasMessagesArg, + bool hasSubfoldersArg, + mail::callback::folderList &callback1Arg, + mail::callback &callback2Arg); + + imapCREATE(string renamePathArg, + string parentPathArg, // path + hiersep + string nameArg, + bool hasMessagesArg, + bool hasSubfoldersArg, + mail::callback::folderList &callback1Arg, + mail::callback &callback2Arg); + + void installed(imap &imapAccount); + +private: + const char *getName(); + void timedOut(const char *errmsg); + + bool untaggedMessage(imap &imapAccount, string msgname); + + bool taggedMessage(imap &imapAccount, string msgname, + string message, + bool okfail, string errmsg); +}; + +LIBMAIL_END + +mail::imapCREATE::imapCREATE(string parentPathArg, + string nameArg, + bool hasMessagesArg, + bool hasSubfoldersArg, + mail::callback::folderList &callback1Arg, + mail::callback &callback2Arg) + : renamePath(""), + parentPath(parentPathArg), + name(nameArg), + hasMessages(hasMessagesArg), + hasSubfolders(hasSubfoldersArg), + callback1(callback1Arg), + callback2(callback2Arg), + listHierSent(false) +{ +} + +mail::imapCREATE::imapCREATE(string renamePathArg, + string parentPathArg, + string nameArg, + bool hasMessagesArg, + bool hasSubfoldersArg, + mail::callback::folderList &callback1Arg, + mail::callback &callback2Arg) + : renamePath(renamePathArg), + parentPath(parentPathArg), + name(nameArg), + hasMessages(hasMessagesArg), + hasSubfolders(hasSubfoldersArg), + callback1(callback1Arg), + callback2(callback2Arg), + listHierSent(false) +{ +} + +void mail::imapCREATE::installed(mail::imap &imapAccount) +{ + // First things first, get the hier separator. + + imapAccount.imapcmd("CREATE", "LIST " + + imapAccount.quoteSimple(parentPath + "tree") + + " \"\"\r\n"); +} + +const char *mail::imapCREATE::getName() +{ + return "CREATE"; +} + +void mail::imapCREATE::timedOut(const char *errmsg) +{ + callbackTimedOut(callback2, errmsg); +} + +bool mail::imapCREATE::untaggedMessage(mail::imap &imapAccount, string msgname) +{ + if (msgname == "LIST") // Untagged LIST replies + { + hiersep.clear(); + imapAccount.installBackgroundTask( new mail::imapLIST(hiersep, + 0, true)); + return true; + } + return false; +} + +bool mail::imapCREATE::taggedMessage(mail::imap &imapAccount, string msgname, + string message, + bool okfail, string errmsg) +{ + if (msgname != "CREATE") + return false; + + if (!okfail) + { + callback2.fail(errmsg); + imapAccount.uninstallHandler(this); + return true; + } + + if (!listHierSent) + { + if (hiersep.size() == 0) + { + callback2.fail("IMAP server failed to process the LIST command."); + imapAccount.uninstallHandler(this); + return true; + } + + + listHierSent=true; + encodedname=name; + + // The folder name should be utf7-encoded + + { + char *p=libmail_u_convert_toutf8(encodedname.c_str(), + unicode_default_chset(), + NULL); + + if (!p && strchr(p, *hiersep[0].getHierSep().c_str()) + != NULL) + { + if (p) + free(p); + callback2.fail("Folder name contains an invalid character."); + imapAccount.uninstallHandler(this); + return true; + } + free(p); + + + p=libmail_u_convert_tobuf(encodedname.c_str(), + unicode_default_chset(), + unicode_x_imap_modutf7, + NULL); + + if (p) + { + try { + encodedname=p; + free(p); + } catch (...) { + free(p); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + } + } + + + imapAccount.imapcmd("CREATE", + (renamePath.size() > 0 + ? string("RENAME ") + + imapAccount.quoteSimple(renamePath) + " ": + string("CREATE ")) + + imapAccount + .quoteSimple(parentPath + + encodedname + + (!hasMessages ? + hiersep[0].getHierSep() + :"")) + + "\r\n"); + return true; + + } + + mail::imapFolder new_folder(imapAccount, parentPath + encodedname, + hiersep[0].getHierSep(), + name, -1); + + new_folder.hasMessages(hasMessages); + new_folder.hasSubFolders(hasSubfolders); + + vector<const mail::folder *> dummy_list; + + dummy_list.push_back(&new_folder); + + callback1.success(dummy_list); + callback2.success(errmsg); + imapAccount.uninstallHandler(this); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// +// Creating a previously known folder, from scratch. This is much easier. + +LIBMAIL_START + +class imapRECREATE : public imapCommandHandler { + + string path; + mail::callback &callback; + +public: + imapRECREATE(string pathArg, mail::callback &callbackArg); + ~imapRECREATE(); + + void installed(imap &imapAccount); + + const char *getName(); + void timedOut(const char *errmsg); + + bool untaggedMessage(imap &imapAccount, string msgname); + + bool taggedMessage(imap &imapAccount, string msgname, + string message, + bool okfail, string errmsg); +}; + +LIBMAIL_END + +mail::imapRECREATE::imapRECREATE(string pathArg, + mail::callback &callbackArg) + : path(pathArg), callback(callbackArg) +{ +} + +mail::imapRECREATE::~imapRECREATE() +{ +} + +void mail::imapRECREATE::installed(mail::imap &imapAccount) +{ + imapAccount.imapcmd("CREATE", "CREATE " + + imapAccount.quoteSimple(path) + "\r\n"); +} + +const char *mail::imapRECREATE::getName() +{ + return "CREATE"; +} + +void mail::imapRECREATE::timedOut(const char *errmsg) +{ + callbackTimedOut(callback, errmsg); +} + +bool mail::imapRECREATE::untaggedMessage(mail::imap &imapAccount, string msgname) +{ + return false; +} + +bool mail::imapRECREATE::taggedMessage(mail::imap &imapAccount, string msgname, + string message, + bool okfail, string errmsg) +{ + if (msgname == "CREATE") + { + if (okfail) + callback.success(errmsg); + else + callback.fail(errmsg); + imapAccount.uninstallHandler(this); + return true; + } + return false; +} + +/////////////////////////////////////////////////////////////////////////// +// +// DELETE a folder. + +LIBMAIL_START + +class imapDELETE : public imapCommandHandler { + mail::callback &callback; + string path; + +public: + imapDELETE(mail::callback &callbackArg, + string pathArg); + + 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::imapDELETE::imapDELETE(mail::callback &callbackArg, + string pathArg) + : callback(callbackArg), path(pathArg) +{ +} + +void mail::imapDELETE::installed(mail::imap &imapAccount) +{ + imapAccount.imapcmd("DELETE", "DELETE " + imapAccount.quoteSimple(path) + + "\r\n"); +} + +const char *mail::imapDELETE::getName() +{ + return "DELETE"; +} + +void mail::imapDELETE::timedOut(const char *errmsg) +{ + callbackTimedOut(callback, errmsg); +} + +bool mail::imapDELETE::untaggedMessage(mail::imap &imapAccount, string name) +{ + return false; +} + +bool mail::imapDELETE::taggedMessage(mail::imap &imapAccount, string name, + string message, + bool okfail, string errmsg) +{ + if (name == "DELETE") + { + if (okfail) + callback.success(errmsg); + else + callback.fail(errmsg); + imapAccount.uninstallHandler(this); + return true; + } + return false; +} + +///////////////////////////////////////////////////////////////////////////// + +void mail::imapFolder::createSubFolder(string name, bool isDirectory, + mail::callback::folderList &callback1, + mail::callback &callback2) const +{ + if (isDestroyed(callback2)) + return; + + if (!imapAccount.ready(callback2)) + return; + + if (imapAccount.smap) + { + mail::smapCREATE *h= + new mail::smapCREATE(path, name, isDirectory, + callback1, callback2); + + if (!h) + { + callback2.fail(strerror(errno)); + return; + } + + try { + imapAccount.installForegroundTask(h); + } catch (...) { + delete h; + throw; + } + + return; + } + + mail::imapCREATE *h=new mail::imapCREATE(path + hiersep, + name, + !isDirectory, + isDirectory, + callback1, + callback2); + if (!h) + { + callback2.fail(strerror(errno)); + return; + } + + try { + imapAccount.installForegroundTask(h); + } catch (...) { + delete h; + throw; + } +} + +void mail::imapFolder::renameFolder(const folder *newParent, + std::string newName, + mail::callback::folderList &callback1, + callback &callback2) const +{ + if (isDestroyed(callback2)) + return; + + if (!imapAccount.ready(callback2)) + return; + + imapAccount.sameServerFolderPtr=NULL; + + string newPath=""; + + if (newParent) + { + newParent->sameServerAsHelperFunc(); + + if (imapAccount.sameServerFolderPtr == NULL) + { + callback2.fail("mail::imapFolder::renameFolder -" + " invalid destination folder object."); + return; + } + + newPath=imapAccount.sameServerFolderPtr->path + + imapAccount.sameServerFolderPtr->hiersep; + } + + imapHandler *h=imapAccount.smap ? + (imapHandler *) + new mail::smapCREATE(path, newPath, newName, + callback1, callback2) : + new mail::imapCREATE(path, newPath, newName, + hasmessages, + hassubfolders, + callback1, + callback2); + + if (!h) + { + callback2.fail(strerror(errno)); + return; + } + + try { + imapAccount.installForegroundTask(h); + } catch (...) { + delete h; + throw; + } +} + +void mail::imapFolder::create(bool isDirectory, mail::callback &callback2) + const +{ + if (isDestroyed(callback2)) + return; + if (!imapAccount.ready(callback2)) + return; + + if (imapAccount.smap) + { + mail::smapCREATE *h= + new mail::smapCREATE(path, isDirectory, callback2); + + if (!h) + { + callback2.fail(strerror(errno)); + return; + } + + try { + imapAccount.installForegroundTask(h); + } catch (...) { + delete h; + throw; + } + return; + } + + mail::imapRECREATE *h=new mail::imapRECREATE(path + (isDirectory ? + hiersep:""), + callback2); + if (!h) + { + callback2.fail(strerror(errno)); + return; + } + try { + imapAccount.installForegroundTask(h); + } catch (...) { + delete h; + throw; + } +} + +void mail::imapFolder::destroy(mail::callback &callback, bool subdirs) const +{ + if (isDestroyed(callback)) + return; + + if (!imapAccount.ready(callback)) + return; + + imapHandler *h=imapAccount.smap ? (imapHandler *) + new mail::smapDELETE(path, subdirs, callback) + : new mail::imapDELETE(callback, + path + (subdirs ? hiersep:"")); + + if (!h) + { + callback.fail(strerror(errno)); + return; + } + try { + imapAccount.installForegroundTask(h); + } catch (...) { + delete h; + throw; + } +} + +void mail::imapFolder::open(mail::callback &openCallback, + mail::snapshot *restoreSnapshot, + mail::callback::folder &folderCallback) const +{ + if (isDestroyed(openCallback)) + return; + imapAccount.openFolder(path, restoreSnapshot, + openCallback, folderCallback); +} + + +mail::folder *mail::imapFolder::clone() const +{ + mail::imapFolder *i=new mail::imapFolder(*this); + + if (i) return i; + return NULL; +} + +string mail::imapFolder::toString() const +{ + string s=""; + + int i; + + for (i=0; i<3; i++) + { + if (i > 0) + s.append(":",1); + + string ss; + + string::iterator b, e; + + switch (i) { + case 0: + ss=path; + break; + case 1: + ss=hiersep; + break; + default: + ss=name; + break; + } + + b=ss.begin(); + e=ss.end(); + + while (b != e) + { + if (*b == '\\' || *b == ':') + s.append("\\", 1); + s.append(&*b, 1); + b++; + } + } + + s += ":"; + if (hasMessages()) + s += "S"; + if (hasSubFolders()) + s += "C"; + + return s; +} + +void mail::imapFolder::getMyRights(callback &getCallback, string &rights) + const +{ + if (isDestroyed(getCallback)) + return; + + if (!imapAccount.ready(getCallback)) + return; + + if (!imapAccount.hasCapability("ACL")) + { + mail::folder::getMyRights(getCallback, rights); + return; + } + + string s=getPath(); + + if (s.find('\r') != std::string::npos || + s.find('\n') != std::string::npos) // Sanity check + { + getCallback.fail(strerror(EINVAL)); + return; + } + + + if (imapAccount.smap) + { + if (imapAccount.getCapability("ACL") == "ACL") + { + // SMAP uses ACL2 + + mail::folder::getMyRights(getCallback, rights); + return; + } + + smapACL *sa=new smapACL(s, rights, getCallback); + + if (!sa) + { + getCallback.fail(strerror(errno)); + return; + } + + try + { + imapAccount.installForegroundTask(sa); + } catch (...) + { + delete sa; + throw; + } + return; + } + + imapGetMyRights *gm=new imapGetMyRights(s, rights, + getCallback); + + if (!gm) + { + getCallback.fail(strerror(errno)); + return; + } + + try + { + imapAccount.installForegroundTask(gm); + } catch (...) { + delete gm; + throw; + } +} + +void mail::imapFolder::getRights(callback &getCallback, + list<pair<string, string> > &rights) const +{ + if (isDestroyed(getCallback)) + return; + + if (!imapAccount.ready(getCallback)) + return; + + if (!imapAccount.hasCapability("ACL")) + { + mail::folder::getRights(getCallback, rights); + return; + } + + string s=getPath(); + + if (s.find('\r') != std::string::npos || + s.find('\n') != std::string::npos) // Sanity check + { + getCallback.fail(strerror(EINVAL)); + return; + } + + if (imapAccount.smap) + { + if (imapAccount.getCapability("ACL") == "ACL") + { + // SMAP uses ACL2 + + mail::folder::getRights(getCallback, rights); + return; + } + + smapGETACL *sa=new smapGETACL(s, rights, getCallback); + + if (!sa) + { + getCallback.fail(strerror(errno)); + return; + } + + try + { + imapAccount.installForegroundTask(sa); + } catch (...) + { + delete sa; + throw; + } + return; + } + + imapGetRights *gr=new imapGetRights(s, rights, + getCallback); + + if (!gr) + { + getCallback.fail(strerror(errno)); + return; + } + + try + { + imapAccount.installForegroundTask(gr); + } catch (...) { + delete gr; + throw; + } +} + +void mail::imapFolder::setRights(callback &setCallback, + string &errorIdentifier, + vector<string> &errorRights, + string identifier, + string rights) const +{ + errorIdentifier=""; + errorRights.clear(); + if (isDestroyed(setCallback)) + return; + + if (!imapAccount.ready(setCallback)) + return; + + if (!imapAccount.hasCapability("ACL")) + { + mail::folder::setRights(setCallback, + errorIdentifier, + errorRights, + identifier, rights); + return; + } + + string s=getPath(); + + if (s.find('\r') != std::string::npos || + s.find('\n') != std::string::npos || + identifier.find('\r') != std::string::npos || + identifier.find('\n') != std::string::npos || + rights.find('\r') != std::string::npos || + rights.find('\n') != std::string::npos) // Sanity check + { + setCallback.fail(strerror(EINVAL)); + return; + } + + if (imapAccount.smap) + { + if (imapAccount.getCapability("ACL") == "ACL") + { + // SMAP uses ACL2 + + mail::folder::setRights(setCallback, + errorIdentifier, + errorRights, + identifier, rights); + return; + } + + smapSETACL *sa=new smapSETACL(s, identifier, rights, + false, + errorIdentifier, + errorRights, + setCallback); + + if (!sa) + { + setCallback.fail(strerror(errno)); + return; + } + + try + { + imapAccount.installForegroundTask(sa); + } catch (...) + { + delete sa; + throw; + } + return; + } + + + + + /* Fix ACL/ACL2 brokenness */ + + if (rights.find(ACL_DELETEMSGS[0]) != std::string::npos && + rights.find(ACL_DELETEFOLDER[0]) != std::string::npos && + rights.find(ACL_EXPUNGE[0]) != std::string::npos) + { + size_t p=rights.find(ACL_DELETEMSGS[0]); + + rights.erase(rights.begin()+p, rights.begin()+p+1); + p=rights.find(ACL_DELETEFOLDER[0]); + rights.erase(rights.begin()+p, rights.begin()+p+1); + p=rights.find(ACL_EXPUNGE[0]); + rights.erase(rights.begin()+p, rights.begin()+p+1); + + rights += ACL_DELETE_SPECIAL; + } + + imapSetRights *sr=new imapSetRights(s, identifier, rights, + false, + errorIdentifier, + errorRights, + setCallback); + + if (!sr) + { + setCallback.fail(strerror(errno)); + return; + } + + try + { + imapAccount.installForegroundTask(sr); + } catch (...) { + delete sr; + throw; + } +} + +void mail::imapFolder::delRights(callback &setCallback, + string &errorIdentifier, + vector<std::string> &errorRights, + string identifier) const +{ + errorIdentifier=""; + errorRights.clear(); + if (isDestroyed(setCallback)) + return; + + if (!imapAccount.ready(setCallback)) + return; + + if (!imapAccount.hasCapability("ACL")) + { + mail::folder::delRights(setCallback, + errorIdentifier, + errorRights, + identifier); + return; + } + + string s=getPath(); + + if (s.find('\r') != std::string::npos || + s.find('\n') != std::string::npos) // Sanity check + { + setCallback.fail(strerror(EINVAL)); + return; + } + + if (imapAccount.smap) + { + if (imapAccount.getCapability("ACL") == "ACL") + { + // SMAP uses ACL2 + + mail::folder::delRights(setCallback, + errorIdentifier, + errorRights, + identifier); + return; + } + + smapSETACL *sa=new smapSETACL(s, identifier, "", + true, + errorIdentifier, + errorRights, + setCallback); + + if (!sa) + { + setCallback.fail(strerror(errno)); + return; + } + + try + { + imapAccount.installForegroundTask(sa); + } catch (...) + { + delete sa; + throw; + } + return; + } + imapSetRights *sr=new imapSetRights(s, identifier, "", + true, + errorIdentifier, + errorRights, + setCallback); + + if (!sr) + { + setCallback.fail(strerror(errno)); + return; + } + + try + { + imapAccount.installForegroundTask(sr); + } catch (...) { + delete sr; + throw; + } +} + + +///////////////////////////////////////////////////////////////////////////// +// +// Adding a new message. + +LIBMAIL_START + +class imapAPPEND : public imapCommandHandler, + public addMessage { + + imap &imapAccount; + mail::callback &callback; + string path; + + bool continuationReceived; + + // Helper class that pushes the temp file's contents to the server. + + class Pusher : public fd::WriteBuffer { + + public: + + imapAPPEND *myappend; + + Pusher(); + ~Pusher(); + + bool fillWriteBuffer(); + }; + + Pusher *myPusher; +public: + friend class Pusher; + + FILE *tmpfileptr; + string appendcmd; + long bytes; + long bytesDone; + long bytesTotal; + + imapAPPEND(imap &imapArg, + mail::callback &callbackArg, + string pathArg); + ~imapAPPEND(); + + void installed(imap &imapAccount); + + + void saveMessageContents(string txt); + void go(); + void fail(string errmsg); + +private: + const char *getName(); + void timedOut(const char *timedout); + bool untaggedMessage(imap &imapAccount, string msgname); + bool taggedMessage(imap &imapAccount, string msgname, + string message, + bool okfail, string errmsg); + bool continuationRequest(imap &imapAccount, string msg); + + bool fillWriteBuffer(); + + +}; + +LIBMAIL_END + +mail::imapAPPEND::imapAPPEND(mail::imap &imapArg, + mail::callback &callbackArg, + string pathArg) + : mail::addMessage(&imapArg), + imapAccount(imapArg), callback(callbackArg), path(pathArg), + continuationReceived(false), myPusher(NULL), tmpfileptr(NULL) +{ +} + +mail::imapAPPEND::~imapAPPEND() +{ + if (tmpfileptr) + fclose(tmpfileptr); + if (myPusher) + myPusher->myappend=NULL; +} + +const char *mail::imapAPPEND::getName() +{ + return "APPEND"; +} + +void mail::imapAPPEND::timedOut(const char *timedout) +{ + callbackTimedOut(callback, timedout); +} + +// The application pushes the message's contents here. Save the contents +// into a temporary file, using CRNL line termination. + +void mail::imapAPPEND::saveMessageContents(string s) +{ + string::iterator b=s.begin(), e=s.end(); + + while (b != e) + { + char c= *b++; + + if (c == '\n') + putc('\r', tmpfileptr); + putc(c, tmpfileptr); + } +} + + +void mail::imapAPPEND::installed(mail::imap &imapAccount) +{ + myPusher=new Pusher(); + + if (!myPusher) + LIBMAIL_THROW("Out of memory."); + + try { + imapAccount.imapcmd("APPEND", appendcmd); + imapAccount.socketWrite(myPusher); + myPusher->myappend=this; + } catch (...) { + delete myPusher; + myPusher=NULL; + } +} + +bool mail::imapAPPEND::untaggedMessage(mail::imap &imapAccount, string msgname) +{ + return false; +} + +bool mail::imapAPPEND::continuationRequest(mail::imap &imapAccount, string msg) +{ + continuationReceived=true; // Tell pusher the show's on. + return true; +} + +bool mail::imapAPPEND::taggedMessage(mail::imap &imapAccount, string name, + string message, + bool okfail, string errmsg) +{ + if (name == "APPEND") + { + if (okfail) + callback.success(errmsg); + else + callback.fail(errmsg); + imapAccount.uninstallHandler(this); + return true; + } + return false; +} + +bool mail::imapAPPEND::fillWriteBuffer() +{ + char buffer[BUFSIZ]; + + if (!continuationReceived) + return true; + + if (bytes <= 0) + { + callback.reportProgress(bytesTotal, bytesTotal, 0, 1); + return false; + } + + int n=fread(buffer, 1, bytes > (long)sizeof(buffer) + ? sizeof(buffer):bytes, tmpfileptr); + + if (n <= 0) + { + n=bytes > (long)sizeof(buffer) ? sizeof(buffer):bytes; + memset(buffer, '\n', n); + } + + bytes -= n; + bytesDone += n; + setTimeout(); + callback.reportProgress(bytesDone, bytesTotal, 0, 1); + myPusher->writebuffer.insert(myPusher->writebuffer.end(), + buffer, buffer+n); + return true; +} + +mail::imapAPPEND::Pusher::Pusher() : myappend(NULL) +{ +} + +mail::imapAPPEND::Pusher::~Pusher() +{ + if (myappend) + myappend->myPusher=NULL; +} + +bool mail::imapAPPEND::Pusher::fillWriteBuffer() +{ + if (!myappend) + return false; + + return myappend->fillWriteBuffer(); +} + +void mail::imap::addSmapFolderCmd(mail::smapAddMessage *add, + std::string path) +{ + vector<string> words; + smapHandler::path2words(path, words); + + vector<string>::iterator b, e; + + string pstr="ADD FOLDER "; + + b=words.begin(); + e=words.end(); + + while (b != e) + { + pstr += " "; + pstr += quoteSMAP( *b ); + b++; + } + + pstr += " \"\"\n"; + + add->cmds.push_back(pstr); +} + +mail::folder *mail::imap::getSendFolder(const smtpInfo &info, + const mail::folder *folder, + std::string &errmsg) +{ + if (!smap) + return mail::account::getSendFolder(info, folder, errmsg); + + if (folder) + { + sameServerFolderPtr=NULL; + folder->sameServerAsHelperFunc(); + if (!sameServerFolderPtr) + return mail::account::getSendFolder(info, folder, + errmsg); + } + + if (info.recipients.size() == 0) + { + errno=ENOENT; + errmsg="Empty recipient list."; + return NULL; + } + + mail::folder *f= + new smapSendFolder(this, info, folder ? folder->getPath():""); + + if (!f) + errmsg=strerror(errno); + return f; +} + +mail::addMessage *mail::imap::addMessage(string path, + mail::callback &callback) +{ + if (smap) + { + mail::smapAddMessage *add=new + smapAddMessage(*this, callback); + + if (!add) + { + callback.fail(strerror(errno)); + return NULL; + } + + try { + addSmapFolderCmd(add, path); + } catch (...) { + delete add; + add=NULL; + } + return add; + } + + mail::imapAPPEND *append=new mail::imapAPPEND(*this, callback, path); + + if (!append) + { + callback.fail(strerror(errno)); + return NULL; + } + + if ((append->tmpfileptr=tmpfile()) == NULL) + { + callback.fail(strerror(errno)); + delete append; + return NULL; + } + + time(&append->messageDate); + + return append; +} + +void mail::imapAPPEND::fail(string errmsg) +{ + callback.fail(errmsg); + delete this; +} + +void mail::imapAPPEND::go() +{ + if (!checkServer()) + return; + + if (!imapAccount.ready(callback)) + { + delete this; + return; + } + + try { + + string flags=imapAccount.messageFlagStr(messageInfo); + + fprintf(tmpfileptr, "\r\n"); + + // Part of the APPEND cmd, actually + + bytesDone=0; + + if (fflush(tmpfileptr) < 0 || ferror(tmpfileptr) + || (bytes=ftell(tmpfileptr)) == -1 + || fseek(tmpfileptr, 0L, SEEK_SET) < 0) + { + callback.fail(strerror(errno)); + delete this; + return; + } + + bytesTotal=bytes; + + string cnt_s; + + { + ostringstream o; + + o << bytes-2; + cnt_s=o.str(); + } + + char buffer[100]; + + rfc822_mkdate_buf(messageDate, buffer); + + char *p=strchr(buffer, ','); + + if (p && *++p && *++p) + { + char *q=strchr(p, ' '); + + if (q) + *q='-'; + + q=strchr(p, ' '); + + if (q) + *q='-'; + } + else + p=buffer; + + appendcmd="APPEND " + imapAccount.quoteSimple(path) + + " (" + flags + ") " + + imapAccount.quoteSimple(p) + + " {" + cnt_s + "}\r\n"; + + imapAccount.installForegroundTask(this); + } catch (...) { + callback.fail(strerror(errno)); + delete this; + } +} + +#if 0 +bool mail::imapFolder:sameServerAs(const mail::folder *f) const +{ + if (isDestroyed()) + return false; + + imapAccount.sameServerHelperFlag=false; + + f->sameServerAsHelperFunc(); + + return imapAccount.sameServerHelperFlag; +} +#endif + +void mail::imapFolder::sameServerAsHelperFunc() const +{ + if (isDestroyed()) + return; + + imapAccount.sameServerFolderPtr=this; +} diff --git a/libmail/imapfolders.H b/libmail/imapfolders.H new file mode 100644 index 0000000..5ccdccb --- /dev/null +++ b/libmail/imapfolders.H @@ -0,0 +1,108 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_imapfolders_H +#define libmail_imapfolders_H + +#include "libmail_config.h" +#include "mail.H" + +LIBMAIL_START + + +class imap; + +/////////////////////////////////////////////////////////////////////////// +// +// The IMAP driver's mail::folder subclass. + +class imapFolder : public mail::folder { + + imap &imapAccount; + +public: + std::string path, hiersep, name; +private: + bool hasmessages, hassubfolders; + int type; + + void sameServerAsHelperFunc() const; + +public: + imapFolder(imap &imapAccount, + std::string pathArg, std::string hierArg, std::string nameArg, + int typeArg); + imapFolder(const imapFolder &); + + imapFolder &operator=(const imapFolder &); + + ~imapFolder(); + + void hasMessages(bool); + void hasSubFolders(bool); + + std::string getName() const; + std::string getPath() const; + bool isParentOf(std::string) const; + + std::string getHierSep() { return hiersep.c_str(); } + void setHierSep(std::string h) { hiersep=h; } + + void setName(const char *c) { name=c; } + int getType() const { return (type); } + + bool hasMessages() const; + bool hasSubFolders() const; + + void readSubFolders(mail::callback::folderList &callback1, + mail::callback &callback2) const; + + void getParentFolder(callback::folderList &callback1, + callback &callback2) const; + + mail::addMessage *addMessage(mail::callback &callback) const; + + void readFolderInfo(mail::callback::folderInfo + &callback1, + mail::callback &callback2) const; + + void createSubFolder(std::string name, bool isDirectory, + mail::callback::folderList &callback1, + mail::callback &callback2) const; + + void create(bool isDirectory, mail::callback &callback) const; + + void destroy(mail::callback &callback, bool destroyDir) const; + + void renameFolder(const folder *newParent, std::string newName, + mail::callback::folderList &callback1, + callback &callback) const; + + mail::folder *clone() const; + std::string toString() const; + + void open(mail::callback &openCallback, + snapshot *restoreSnapshot, + mail::callback::folder &folderCallback) const; + + void getMyRights(callback &getCallback, std::string &rights) const; + void getRights(callback &getCallback, + std::list<std::pair<std::string, std::string> > + &rights) const; + + void setRights(callback &setCallback, + std::string &errorIdentifier, + std::vector<std::string> &errorRights, + std::string identifier, + std::string rights) const; + void delRights(callback &setCallback, + std::string &errorIdentifier, + std::vector<std::string> &errorRights, + std::string identifier) const; +}; + +LIBMAIL_END + +#endif diff --git a/libmail/imaphandler.C b/libmail/imaphandler.C new file mode 100644 index 0000000..b163917 --- /dev/null +++ b/libmail/imaphandler.C @@ -0,0 +1,354 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "imap.H" +#include "imaphandler.H" +#include <stdlib.h> +#include <string.h> +#include <ctype.h> + +using namespace std; + +////////////////////////////////////////////////////////////////////////// +// +// Tokenize server response +// + +int mail::imapHandlerStructured::process(mail::imap &imapAccount, string &buffer) +{ + string::iterator b=buffer.begin(); + string::iterator e=buffer.end(); + int cnt=0; + + errbuf=buffer; + + if (stringmode) // Collecting a string + { + string s=""; + + while (b != e) + { + if (*b == '"') + { + stringmode=0; + largestringsize=s.size(); + process(imapAccount, Token(STRING, s)); + return cnt+1; + } + + if (*b == '\\') + { + b++; + cnt++; + if (b == e) + break; + } + if (*b == '\n') + { + stringmode=0; + process(imapAccount, Token(EOL)); + imapAccount.uninstallHandler(this); + return cnt+1; + } + + if (s.length() < 10000) + s.append(&*b, 1); + ++b; + ++cnt; + } + return 0; + } + + if (largestringleft > 0) // Collecting {nnn}\r\n strings + { + size_t n=buffer.length(); + + if (n > largestringleft) + n=largestringleft; + + string buffer_cpy=buffer; + + buffer_cpy.erase(n); + + largestringleft -= n; + + setTimeout(); // Don't time me out just yet... + + if (wantLargeString()) + { + setTimeout(); + + if (!errormode) + { + if (largestringleft == 0) + process(imapAccount, Token(STRING, + buffer_cpy)); + else // More later... + process(imapAccount, Token(LARGESTRING, + buffer_cpy)); + } + return n; + } + + // Subclass is not prepared to handle {nnn}\r\n strings. + // For simplicity, they are converted to regular strings, + // but to guard against hostile servers truncate large + // strings at 8K + + stringbuffer += buffer_cpy; + + if (stringbuffer.length() > 8192) + stringbuffer.erase(8192); + + setTimeout(); + if (largestringleft == 0 && !errormode) + process(imapAccount, Token(STRING, stringbuffer)); + return n; + } + + while (b != e) + { + if (*b == '\n') + { + setTimeout(); + if (!donemode && !errormode) + process(imapAccount, Token(EOL)); + imapAccount.uninstallHandler(this); + return (cnt+1); + } + + if (unicode_isspace((unsigned char)*b)) + { + b++; + cnt++; + continue; + } + + if (*b == '{') // Possibly a large string? + { + size_t n=0; + + b++; + cnt++; + + for (;;) + { + if (b == e) + return 0; + + if (*b == '}') + { + b++; + cnt++; + if (b == e) + return 0; + + if (*b == '\r') + { + b++; + cnt++; + if (b == e) + return 0; + } + + if (*b != '\n') + break; + + b++; + cnt++; + + largestringsize=n; + largestringleft=n; + stringbuffer=""; + + if (donemode && !errormode) + { + error(imapAccount); + return cnt; + } + + setTimeout(); + if (!errormode && n == 0) + // Make sure we recognize a + // 0-length string + { + process(imapAccount, + Token(STRING, + stringbuffer)); + } + return cnt; + } + + if (!isdigit((int)(unsigned char)*b)) + break; + + n=n * 10 + (*b-'0'); + b++; + cnt++; + } + + if (!errormode) + error(imapAccount); + return cnt; + } + if (errormode) + // Recovering from an error, just eat input until \n. + { + b++; + cnt++; + continue; + } + + + if (donemode) + { + error(imapAccount); + continue; + } + + if (strchr("*()", *b) != NULL) + { + setTimeout(); + process(imapAccount, Token((int)(char)*b)); + return cnt+1; + } + + if (*b == '"') + { + stringmode=1; + return cnt+1; + } + +#define ATOMCHAR(c) ATOMCHAR2((int)(unsigned char)(c)) +#define ATOMCHAR2(c) ( (c) > ' ' && (c) != '"' && \ + (c) != '(' && (c) != ')' && \ + (c) != '{' && (c) != '%' && (c) != '*') + + if (ATOMCHAR(*b)) // ATOM + { + string s=""; + + s.append(&*b, 1); + b++; + cnt++; + + // Swallow up BODY[HEADER.FIELDS (list)] + + unsigned bracket_cnt=0; + + while (b != e) + { + if (bracket_cnt > 0) + { + if ( *b == '\n' || *b == '\r') + { + process(imapAccount, + Token(ATOM, s)); + return cnt; + } + + if (s.length() < 10000) + s.append(&*b, 1); + + if ( *b == ']' ) + { + if (--bracket_cnt == 0) + { + process(imapAccount, + Token(ATOM, s) + ); + return cnt+1; + } + } + ++b; + ++cnt; + + continue; + } + + if (!ATOMCHAR(*b)) + { + setTimeout(); + + if (strcasecmp(s.c_str(), "NIL") == 0) + process(imapAccount, Token(NIL)); + else + process(imapAccount, Token(ATOM, s)); + return cnt; + } + + if (*b == '[') + ++bracket_cnt; + + if (s.length() < 10000) + s.append(&*b, 1); + ++b; + ++cnt; + } + return (0); + } + + error(imapAccount); + return cnt+1; + } + + return cnt; +} + +bool mail::imapHandlerStructured::wantLargeString() +{ + return false; // The default, only some subclasses have large string + // handler code +} + +void mail::imapHandlerStructured::done() +{ + donemode=1; +} + +// +// Go into error recovery mode, eating input until next endofline + +void mail::imapHandlerStructured::error(mail::imap &imapAccount) +{ + if (errbuf.length() > 32) + { + errbuf.erase(32); + errbuf.append("..."); + } + + size_t p=errbuf.find('\n'); + + if (p < std::string::npos) + errbuf.erase(p); + + imapAccount.fatalError("IMAP reply parse failure: ... " + errbuf); + errormode=1; +} + + +mail::imapHandlerStructured::Token::operator string() +{ + switch ((enum TokenType)token) { + case EOL: + return "EOL"; + case ATOM: + return text; + case STRING: + case LARGESTRING: + return ( "STRING[" + text + "]"); + case NIL: + return ( "NIL" ); + } + + char c[4]; + + c[0]='\''; + c[1]=(char)token; + c[2]='\''; + c[3]=0; + + return c; +} diff --git a/libmail/imaphandler.H b/libmail/imaphandler.H new file mode 100644 index 0000000..222b791 --- /dev/null +++ b/libmail/imaphandler.H @@ -0,0 +1,86 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_imaphandler_H +#define libmail_imaphandler_H + +#include "libmail_config.h" +#include "imap.H" + +LIBMAIL_START + +////////////////////////////////////////////////////////////////////////// +// +// Something that tokenizes server response. +// + +class imapHandlerStructured : public imapHandler { + + int errormode; + int donemode; + int stringmode; + + size_t largestringleft; + std::string errbuf; + std::string stringbuffer; + +protected: + size_t largestringsize; +public: + imapHandlerStructured(int timeoutVal=60) + : imapHandler(timeoutVal), errormode(0), + donemode(0), stringmode(0), largestringleft(0) + { + } + + enum TokenType { + EOL=256, + ATOM, + STRING, + LARGESTRING, // Another LARGESTRING, or STRING will follow + NIL + }; + + class Token { + public: + int token; + std::string text; + + Token(enum TokenType t, std::string s) : token(t), text(s) {} + Token(int t, std::string s) : token(t), text(s) {} + Token(enum TokenType t) : token((int)t) {} + Token(int t) : token(t) {} + + operator int() { return token; } + + bool operator== (int t) { return t == token; } + bool operator== (enum TokenType t) { return (int)t == token; } + bool operator!= (int t) { return t != token; } + bool operator!= (enum TokenType t) { return (int)t != token; } + + operator std::string(); + }; + + virtual void error(imap &); + virtual void done(); +protected: + + virtual int process(imap &imapAccount, std::string &buffer); +private: + virtual void process(imap &imapAccount, Token t)=0; + + virtual bool wantLargeString(); + + // Subclass should return true if it's willing to accept {nnn}\r\n + // piecemeal, using LARGESTRING tokens. + // Otherwise, any {nnn}\r\n is converted to a string (up to some + // reasonable limit) + + +}; + +LIBMAIL_END + +#endif diff --git a/libmail/imaphmac.C b/libmail/imaphmac.C new file mode 100644 index 0000000..bbf0740 --- /dev/null +++ b/libmail/imaphmac.C @@ -0,0 +1,45 @@ +/* +** Copyright 2002-2005, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "imaphmac.H" +#include "libhmac/hmac.h" + +static mail::imaphmac md5(hmac_md5, "MD5"); +static mail::imaphmac sha1(hmac_sha1, "SHA1"); +static mail::imaphmac sha256(hmac_sha256, "SHA256"); + +const mail::imaphmac * const mail::imaphmac::hmac_methods[]= + { &sha256, &sha1, &md5, NULL}; + + +mail::imaphmac::imaphmac(const struct hmac_hashinfo &hmacArg, + const char *nameArg) + : hmac(hmacArg), name(nameArg) +{ +} + +mail::imaphmac::~imaphmac() +{ +} + +std::string mail::imaphmac::operator()(std::string password, std::string challenge) const +{ + std::string i, o, b; + + i.insert(i.end(), hmac.hh_L, (char)0); + o.insert(o.end(), hmac.hh_L, (char)0); + b.insert(b.end(), hmac.hh_L, (char)0); + + hmac_hashkey( &hmac, &*password.begin(), password.size(), + (unsigned char *)&*o.begin(), + (unsigned char *)&*i.begin()); + + hmac_hashtext( &hmac, &*challenge.begin(), challenge.size(), + (unsigned char *)&*o.begin(), + (unsigned char *)&*i.begin(), + (unsigned char *)&*b.begin()); + return (b); +} diff --git a/libmail/imaphmac.H b/libmail/imaphmac.H new file mode 100644 index 0000000..50b2ec6 --- /dev/null +++ b/libmail/imaphmac.H @@ -0,0 +1,44 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_imaphmac_h +#define libmail_imaphmac_h + +#include "libmail_config.h" +#include "namespace.H" + +#include <string> + +struct hmac_hashinfo; + +/////////////////////////////////////////////////////////////////////////// +// +// Helper class for calculating HMAC hashes. + +LIBMAIL_START + +class imaphmac { + + const struct hmac_hashinfo &hmac; // The HMAC function + const char *name; // The name of this function. + +public: + imaphmac(const struct hmac_hashinfo &hmacArg, + const char *nameArg); + + ~imaphmac(); + + std::string operator()(std::string password, + std::string challenge) const; + + static const imaphmac * const hmac_methods[]; + + const char *getName() const { return (name); } + +}; + +LIBMAIL_END + +#endif diff --git a/libmail/imapidle.C b/libmail/imapidle.C new file mode 100644 index 0000000..2409754 --- /dev/null +++ b/libmail/imapidle.C @@ -0,0 +1,197 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "imap.H" +#include "imaphandler.H" +#include "imapidle.H" + +using namespace std; + +mail::imapIdleHandler::imapIdleHandler(bool idleOnOffArg, + mail::callback *callbackArg) + : idleOnOff(idleOnOffArg), idling(false), + shouldTerminate(false), terminating(false), + callback(callbackArg) +{ +} + +const char *mail::imapIdleHandler::getName() +{ + return "IDLE"; +} + +bool mail::imapIdleHandler::getTimeout(imap &imapAccount, + int &ioTimeout) +{ + if (waiting) + { + struct timeval tv; + + gettimeofday(&tv, NULL); + + if (tv.tv_sec > waitingUntil.tv_sec || + (tv.tv_sec == waitingUntil.tv_sec && + tv.tv_usec >= waitingUntil.tv_usec)) + { + waiting=false; + + imapAccount.imapcmd("IDLE", "IDLE\r\n"); + ioTimeout=0; + return false; + } + else + { + struct timeval t=waitingUntil; + + t.tv_usec -= tv.tv_usec; + + if (t.tv_usec < 0) + { + t.tv_usec += 1000000; + --t.tv_sec; + } + t.tv_sec -= tv.tv_sec; + + ioTimeout=t.tv_sec * 1000 + t.tv_usec / 1000; + + if (ioTimeout == 0) + ioTimeout=100; + return false; + } + } + + ioTimeout = 15 * 60 * 1000; + return false; +} + +void mail::imapIdleHandler::timedOut(const char *errmsg) +{ + mail::callback *c=callback; + + callback=NULL; + + if (c) + callbackTimedOut(*c, errmsg); +} + +mail::imapIdleHandler::~imapIdleHandler() +{ + mail::callback *c=callback; + + callback=NULL; + + if (c) + c->success("OK"); +} + +bool mail::imapIdleHandler::untaggedMessage(imap &imapAccount, + std::string name) +{ + return false; +} + +void mail::imapIdleHandler::installed(imap &imapAccount) +{ + if (!idleOnOff || !imapAccount.wantIdleMode + || shouldTerminate || !imapAccount.task_queue.empty()) + { + imapAccount.uninstallHandler(this); + return; + } + + waiting=true; + + // Wait 1/10th of a second before issuing an IDLE. When we're in + // IDLE mode, and a new task is started, we terminate and a requeue + // ourselves after the pending command. We don't want to immediately + // reenter the IDLE mode because if the first task queues up a second + // task we would have to immediately get out of IDLE mode right away, + // so wait 1/10th of a second to see if anything is up. + + gettimeofday(&waitingUntil, NULL); + if ((waitingUntil.tv_usec += 100000) > 1000000) + { + waitingUntil.tv_usec %= 1000000; + ++waitingUntil.tv_sec; + } +} + + +bool mail::imapIdleHandler::taggedMessage(imap &imapAccount, std::string name, + std::string message, + bool okfail, std::string errmsg) +{ + mail::callback *c=callback; + + callback=NULL; + + imapAccount.uninstallHandler(this); + + if (imapAccount.wantIdleMode) + { + // Reinstall, when we're done + + imapAccount.installForegroundTask(new imapIdleHandler(true, + NULL)); + c=NULL; + } + + if (c) + { + if (okfail) + c->success(errmsg); + else + c->fail(errmsg); + } + return true; +} + +bool mail::imapIdleHandler::continuationRequest(imap &imapAccount, + std::string request) +{ + idling=true; + if (shouldTerminate) + terminateIdle(imapAccount); + + mail::callback *c=callback; + + callback=NULL; + + if (c) + c->success("Monitoring folder for changes..."); + + return true; +} + +void mail::imapIdleHandler::anotherHandlerInstalled(imap &imapAccount) +{ + if (waiting) + { + imapAccount.uninstallHandler(this); + return; + } + + shouldTerminate=true; + if (idling) + terminateIdle(imapAccount); +} + +void mail::imapIdleHandler::terminateIdle(imap &imapAccount) +{ + if (terminating) + return; + terminating=true; + + mail::callback *c=callback; + + callback=NULL; + + if (c) + c->success("OK"); + + imapAccount.imapcmd("", "DONE\r\n"); +} + diff --git a/libmail/imapidle.H b/libmail/imapidle.H new file mode 100644 index 0000000..90ecde4 --- /dev/null +++ b/libmail/imapidle.H @@ -0,0 +1,60 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ + +#ifndef libmail_imapidle_H +#define libmail_imapidle_H + +#include "libmail_config.h" + +#include "mail.H" +#include "imap.H" + +#include <time.h> + +LIBMAIL_START + +class imapIdleHandler : public imapCommandHandler { + + bool idleOnOff; + + bool idling; + bool shouldTerminate; + bool terminating; + + bool waiting; + struct timeval waitingUntil; + + mail::callback *callback; + + void terminateIdle(imap &); + + const char *getName(); + void timedOut(const char *errmsg); + + + bool getTimeout(imap &, int &); + +public: + imapIdleHandler(bool idleOnOffArg, mail::callback *callbackArg); + ~imapIdleHandler(); + + void installed(imap &); + + bool untaggedMessage(imap &imapAccount, std::string name); + + bool taggedMessage(imap &imapAccount, std::string name, + std::string message, + bool okfail, std::string errmsg); + + bool continuationRequest(imap &imapAccount, + std::string request); + + void anotherHandlerInstalled(imap &imapAccount); +}; + + +LIBMAIL_END +#endif diff --git a/libmail/imaplisthandler.C b/libmail/imaplisthandler.C new file mode 100644 index 0000000..cc0dc09 --- /dev/null +++ b/libmail/imaplisthandler.C @@ -0,0 +1,498 @@ +/* +** Copyright 2002-2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "imaplisthandler.H" +#include "imapfolders.H" +#include "smaplist.H" +#include "misc.H" +#include "unicode/unicode.h" +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> + +using namespace std; + +// Invoked from mail::imapFolder::readSubFolders() + +void mail::imap::readSubFolders(string path, + mail::callback::folderList &callback1, + mail::callback &callback2) +{ + if (!ready(callback2)) + return; + + if (smap) + { + mail::smapLIST *s=new mail::smapLIST(path, callback1, + callback2); + + installForegroundTask(s); + } + else + { + mail::imapListHandler *h= + new mail::imapListHandler(callback1, callback2, path, + false); + installForegroundTask(h); + } +} + +void mail::imap::findFolder(string folder, + mail::callback::folderList &callback1, + mail::callback &callback2) +{ + if (!ready(callback2)) + return; + + if (folder.size() == 0) // Top level hierarchy + { + mail::imapFolder f(*this, "", "", "", -1); + + f.hasMessages(false); + f.hasSubFolders(true); + vector<const mail::folder *> list; + list.push_back(&f); + + callback1.success(list); + callback2.success("OK"); + return; + } + + installForegroundTask( smap ? + (imapHandler *) + new mail::smapLISToneFolder(folder, + callback1, + callback2) + : new mail::imapListHandler(callback1, + callback2, + folder, + true)); +} + +string mail::imap::translatePath(string path) +{ + if (!smap) + { + char *p=libmail_u_convert_tobuf(path.c_str(), + unicode_default_chset(), + unicode_x_imap_modutf7, NULL); + + if (p) + { + try + { + path=p; + free(p); + return path; + } catch (...) { + free(p); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + } + + return ""; + } + + vector<string> words; + + do + { + string component; + + size_t n=path.find('/'); + + if (n == std::string::npos) + { + component=path; + path=""; + } + else + { + component=path.substr(0, n); + path=path.substr(n+1); + } + + vector<unicode_char> ucvec; + + unicode_char *uc; + size_t ucsize; + libmail_u_convert_handle_t h; + + if ((h=libmail_u_convert_tou_init(unicode_default_chset(), + &uc, &ucsize, 1)) == NULL) + { + uc=NULL; + } + else + { + libmail_u_convert(h, component.c_str(), + component.size()); + + if (libmail_u_convert_deinit(h, NULL)) + uc=NULL; + } + + if (!uc) + { + errno=EINVAL; + return ""; + } + + try { + size_t n; + + for (n=0; uc[n]; n++) + ; + + ucvec.insert(ucvec.end(), uc, uc+n); + + for (n=0; n<ucvec.size(); ) + { + if (ucvec[n] == '\\' && n+1 < ucvec.size()) + { + ucvec.erase(ucvec.begin()+n, + ucvec.begin()+n+1); + n++; + continue; + } + if (ucvec[n] == '%') + { + unicode_char ucnum=0; + size_t o=n+1; + + while (o < ucvec.size()) + { + if ((unsigned char)ucvec[o] + != ucvec[o]) + break; + if (!isdigit(ucvec[o])) + break; + + ucnum=ucnum * 10 + + ucvec[o]-'0'; + ++o; + } + + if (o < ucvec.size() && + ucvec[o] == ';') + ++o; + + ucvec[n++]=ucnum; + ucvec.erase(ucvec.begin()+n, + ucvec.begin()+o); + continue; + } + n++; + } + free(uc); + } catch (...) { + free(uc); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + char *p; + size_t psize; + + if ((h=libmail_u_convert_fromu_init("utf-8", &p, &psize, 1)) + != NULL) + { + libmail_u_convert_uc(h, &ucvec[0], ucvec.size()); + if (libmail_u_convert_deinit(h, NULL)) + p=NULL; + } + + if (!p) + { + errno=EINVAL; + return ""; + } + + try { + words.push_back(p); + free(p); + } catch (...) { + free(p); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + } while (path.size() > 0); + + vector<const char *> wordsp; + + vector<string>::iterator wb=words.begin(), we=words.end(); + + while (wb != we) + { + wordsp.push_back( wb->c_str() ); + wb++; + } + + return mail::smapHandler::words2path(wordsp); +} + +mail::imapListHandler:: +imapListHandler(mail::callback::folderList &myCallback, + mail::callback &myCallback2, + string myHier, bool oneFolderOnlyArg) + : callback1(myCallback), callback2(myCallback2), hier(myHier), + oneFolderOnly(oneFolderOnlyArg), fallbackOneFolderOnly(false) +{ +} + +void mail::imapListHandler::installed(mail::imap &imapAccount) +{ + const char *sep="%"; + + if (oneFolderOnly) + sep=""; + + imapAccount.imapcmd("LIST", "LIST \"\" " + + imapAccount.quoteSimple(hier + sep) + "\r\n"); +} + +mail::imapListHandler::~imapListHandler() +{ +} + +const char *mail::imapListHandler::getName() +{ + return "LIST"; +} + +void mail::imapListHandler::timedOut(const char *errmsg) +{ + callback2.fail(errmsg ? errmsg:"Server timed out."); +} + +bool mail::imapListHandler::untaggedMessage(mail::imap &imapAccount, string name) +{ + if (name != "LIST") + return false; + imapAccount.installBackgroundTask( new mail::imapLIST(folders, + hier.length(), + oneFolderOnly)); + return true; +} + +bool mail::imapListHandler::taggedMessage(mail::imap &imapAccount, string name, + string message, + bool okfail, + string errmsg) +{ + if (name != "LIST") + return false; + + if (okfail && oneFolderOnly && !fallbackOneFolderOnly && + folders.size() == 0) + { + fallbackOneFolderOnly=true; + imapAccount.imapcmd("LIST", + "LIST " + + imapAccount.quoteSimple(hier) + + " \"\"\r\n"); + return true; + } + + if (okfail) + { + vector<const mail::folder *> folderPtrs; + + vector<mail::imapFolder>::iterator b, e; + + b=folders.begin(); + e=folders.end(); + + while (b != e) + { + if (fallbackOneFolderOnly) + { + b->path=hier; + } + + folderPtrs.push_back( &*b); + b++; + } + + callback1.success(folderPtrs); + callback2.success("OK"); + } + else + callback2.fail(errmsg); + + imapAccount.uninstallHandler(this); + return true; +} + +////////////////////////////////////////////////////////////////////////// +// +// Untagged LIST parser + +mail::imapLIST::imapLIST(vector<mail::imapFolder> &mylist, + size_t pfixLengthArg, + bool oneNameOnlyArg) + : folderList(mylist), pfixLength(pfixLengthArg), + oneNameOnly(oneNameOnlyArg), + next_func(&mail::imapLIST::start_attribute_list), + hasChildren(false), hasNoChildren(false), marked(false), + unmarked(false), noSelect(false) +{ +} + +mail::imapLIST::~imapLIST() +{ +} + +void mail::imapLIST::installed(mail::imap &imapAccount) +{ +} + +const char *mail::imapLIST::getName() +{ + return ("* LIST"); +} + +void mail::imapLIST::timedOut(const char *errmsg) +{ +} + +void mail::imapLIST::process(mail::imap &imapAccount, Token t) +{ + (this->*next_func)(imapAccount, t); +} + +void mail::imapLIST::start_attribute_list(mail::imap &imapAccount, Token t) +{ + if (t == NIL) + next_func= &mail::imapLIST::get_hiersep; + else if (t == '(') + next_func= &mail::imapLIST::get_attribute; + else + error(imapAccount); +} + +void mail::imapLIST::get_attribute(mail::imap &imapAccount, Token t) +{ + if (t == ATOM) + { + string atom=t; + + upper(atom); + + if (atom == "\\HASCHILDREN") + hasChildren=true; + else if (atom == "\\HASNOCHILDREN") + hasNoChildren=true; + else if (atom == "\\NOSELECT") + noSelect=true; + else if (atom == "\\MARKED") + marked=true; + else if (atom == "\\UNMARKED") + unmarked=true; + else if (atom == "\\NOINFERIORS") + hasNoChildren=true; + return; + } + else if (t == ')') + next_func= &mail::imapLIST::get_hiersep; + else + error(imapAccount); +} + +void mail::imapLIST::get_hiersep(mail::imap &imapAccount, Token t) +{ + if (t == ATOM || t == STRING || t == NIL) + { + hiersep= t.text; + next_func= &mail::imapLIST::get_name; + } + else error(imapAccount); +} + +void mail::imapLIST::get_name(mail::imap &imapAccount, Token t) +{ + if (t == ATOM || t == STRING) + { + string nameVal; + + nameVal= t.text; + + if (oneNameOnly) + { + if (hiersep.size() > 0) + { + size_t p=nameVal.rfind(hiersep[0]); + + if (p != std::string::npos) + nameVal=nameVal.substr(p); + } + } + else + { + if (pfixLength <= nameVal.length()) + nameVal.erase(0, pfixLength); + } + char *p=libmail_u_convert_tobuf(nameVal.c_str(), + unicode_x_imap_modutf7, + unicode_default_chset(), + NULL); + + try { + nameVal=p; + free(p); + } catch (...) { + free(p); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + if (nameVal.size() == 0 && !oneNameOnly) // LIST % artifact + { + done(); + return; + } + + mail::imapFolder f(imapAccount, t.text, hiersep, nameVal, -1); + + f.hasMessages(!noSelect); + f.hasSubFolders(noSelect || hasChildren); + folderList.push_back(f); + next_func= &mail::imapLIST::get_xattr_start; + xattributes.begin(); + } + else + error(imapAccount); +} + +void mail::imapLIST::get_xattr_start(mail::imap &imapAccount, Token t) +{ + if (t == EOL) + { + done(); + return; + } + get_xattr_do(imapAccount, t); +} + +void mail::imapLIST::get_xattr_do(mail::imap &imapAccount, Token t) +{ + bool err_flag; + + next_func= &mail::imapLIST::get_xattr_do; + + if (!xattributes.process(imapAccount, t, err_flag)) + return; + + if (err_flag) + { + error(imapAccount); + return; + } + processExtendedAttributes(folderList.end()[-1]); + done(); +} + +void mail::imapLIST::processExtendedAttributes(mail::imapFolder &folder) +{ +} diff --git a/libmail/imaplisthandler.H b/libmail/imaplisthandler.H new file mode 100644 index 0000000..a71da9a --- /dev/null +++ b/libmail/imaplisthandler.H @@ -0,0 +1,94 @@ +/* +** Copyright 2002-2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_imaplisthandler_H +#define libmail_imaplisthandler_H + +#include "libmail_config.h" +#include "imaphandler.H" +#include "imapfolders.H" +#include "imapparsefmt.H" + +#include <vector> + +/////////////////////////////////////////////////////////////////////////// +// +// A list command. + +LIBMAIL_START + +class imapListHandler : public imapCommandHandler { + + mail::callback::folderList &callback1; + mail::callback &callback2; + std::string hier; + + bool oneFolderOnly; + bool fallbackOneFolderOnly; + + // True if this LIST command is invoked to determine the folder + // hierarchy separator. Otherwise, the LIST command is invoked + // to obtain a list of subfolders. + + std::vector <imapFolder> folders; + +public: + imapListHandler(mail::callback::folderList &myCallback, + mail::callback &myCallback2, + std::string myHier, bool oneFolderOnlyArg); + ~imapListHandler(); + const char *getName(); + void timedOut(const char *errmsg); + + void installed(imap &imapAccount); +private: + + bool untaggedMessage(imap &imapAccount, std::string name); + bool taggedMessage(imap &imapAccount, std::string name, std::string message, + bool okfail, std::string errmsg); +}; + + +// Untagged LIST parser + +class imapLIST : public imapHandlerStructured { + + std::vector <imapFolder> &folderList; + size_t pfixLength; + bool oneNameOnly; + + void (imapLIST::*next_func)(imap &, Token); + + bool hasChildren, hasNoChildren, marked, unmarked, noSelect; + std::string hiersep; + +protected: + imapparsefmt xattributes; +public: + imapLIST(std::vector<imapFolder> &mylist, size_t pfixLengthArg, + bool oneNameOnlyArg=false); + ~imapLIST(); + + void installed(imap &imapAccount); + +private: + const char *getName(); + void timedOut(const char *errmsg); + + void process(imap &imapAccount, Token t); + + void start_attribute_list(imap &imapAccount, Token t); + void get_attribute(imap &imapAccount, Token t); + void get_hiersep(imap &imapAccount, Token t); + void get_name(imap &imapAccount, Token t); + void get_xattr_start(imap &imapAccount, Token t); + void get_xattr_do(imap &imapAccount, Token t); + + virtual void processExtendedAttributes(imapFolder &); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/imaplogin.C b/libmail/imaplogin.C new file mode 100644 index 0000000..2c4721c --- /dev/null +++ b/libmail/imaplogin.C @@ -0,0 +1,1128 @@ +/* +** Copyright 2002-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "logininfo.H" +#include "imaplogin.H" +#include "imaphandler.H" +#include "base64.H" +#include "imaphmac.H" +#include "misc.H" +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <stdio.h> + +#include <algorithm> +#include <iostream> +#include <iomanip> +#include <sstream> +#include <vector> +#include <list> +#include "libcouriertls.h" + +using namespace std; + +//////////////////////////////////////////////////////////////////////// +// +// Helper class that parses the server reply to a login request. + +LIBMAIL_START + +class imapLoginHandler : public imapCommandHandler, + public loginInfo::callbackTarget { + +private: + mail::loginInfo myLoginInfo; + int completed; + string currentCmd; + bool preauthenticated; + + const char *getName(); + void timedOut(const char *errmsg); + + void (imapLoginHandler::*next_login_func)(imap &imapAccount, + string failmsg); + + size_t next_cram_func; + + string loginMethod; + + void loginInfoCallback(std::string); + void loginInfoCallbackCancel(); + + void (imapLoginHandler::*loginCallbackFunc)(std::string); + + void loginCallbackUid(std::string); + void loginCallbackPwd(std::string); + +public: + imapLoginHandler(mail::loginInfo myLoginInfo, + bool preauthenticatedArg); + ~imapLoginHandler(); + + void installed(imap &imapAccount); + void installedtls(imap &imapAccount); + void installedtlsnonext(imap &imapAccount); + + friend class imapCRAMHandler; + +private: + + void logincram(imap &imapAccount, string errmsg); + void logincmd(imap &imapAccount, string errmsg); + void failcmd(imap &imapAccount, string message); + + bool untaggedMessage(imap &imapAccount, string name); + bool taggedMessage(imap &imapAccount, string name, string message, + bool okfail, string errmsg); + + bool continuationRequest(imap &imapAccount, string msg); + +private: + list<string> cmds; +} ; + +//////////////////////////////////////////////////////////////////////// +// +// Helper class that handles CRAM authentication + +class imapCRAMHandler : public imapCommandHandler { + + const char *getName(); + void timedOut(const char *errmsg); + + const mail::loginInfo &myLoginInfo; + imapLoginHandler &loginHandler; + const imaphmac &hmac; + +public: + imapCRAMHandler( const mail::loginInfo &loginInfoArg, + imapLoginHandler &loginHandlerArg, + const imaphmac &hmacArg); + ~imapCRAMHandler(); + + void installed(imap &imapAccount); + +private: + bool untaggedMessage(imap &imapAccount, string name); + bool taggedMessage(imap &imapAccount, string name, string message, + bool okfail, string errmsg); + + bool continuationRequest(imap &imapAccount, string request); +}; + +//////////////////////////////////////////////////////////////////////// +// +// Helper class that parses the server NAMESPACE reply. + +class imapNAMESPACE : public imapHandlerStructured { + + void (imapNAMESPACE::*next_func)(imap &, Token); + int namespace_type; + + string hier; + int string_cnt; + +public: + imapNAMESPACE() + : next_func(&imapNAMESPACE::start_namespace_list), + namespace_type(0) {} + + void installed(imap &imapAccount) {} + +private: + const char *getName() { return ("NAMESPACE"); } + void timedOut(const char *errmsg) {} + void process(imap &imapAccount, Token t); + + + void start_namespace_list(imap &imapAccount, Token t); + void start_namespace_entry(imap &imapAccount, Token t); + void process_namespace_entry(imap &imapAccount, Token t); +} ; + +LIBMAIL_END + +////////////////////////////////////////////////////////////////////////////// +// +// The login process + + +// Wait for a greeting + +mail::imapGreetingHandler:: +imapGreetingHandler(mail::loginInfo myLoginInfoArg): + myLoginInfo(myLoginInfoArg), capability_sent(0), completed(0), + preauthenticated(false) +{ +} + +void mail::imapGreetingHandler::timedOut(const char *errmsg) +{ + completed=1; + callbackTimedOut(*myLoginInfo.callbackPtr, errmsg); +} + +const char *mail::imapGreetingHandler::getName() +{ + return "GREETINGS"; +} + +mail::imapGreetingHandler::~imapGreetingHandler() +{ + if (!completed) + myLoginInfo.callbackPtr->fail("Failed connecting to server."); +} + +void mail::imapGreetingHandler::installed(mail::imap &imapAccount) // Background task +{ +} + +void mail::imapGreetingHandler::error(mail::imap &imapAccount, string errmsg) +{ + completed=1; + mail::callback *callback=myLoginInfo.callbackPtr; + imapAccount.uninstallHandler(this); + callback->fail(errmsg); +} + +int mail::imapGreetingHandler::process(mail::imap &imapAccount, string &buffer) +{ + size_t p=buffer.find('\n'); + + if (p == std::string::npos) + { + if (buffer.size() > 16000) + return buffer.size() - 16000; + // SANITY CHECK - DON'T LET HOSTILE SERVER DOS us + + return (0); + } + + string buffer_cpy=buffer; + + buffer_cpy.erase(p); + ++p; + + string::iterator b=buffer_cpy.begin(); + string::iterator e=buffer_cpy.end(); + + while (b != e) + { + if (!unicode_isspace((unsigned char)*b)) + break; + ++b; + } + + if (b == e) + return 0; + + if (*b == '*') + { + ++b; + + string status=imapAccount.get_word(&b, &e); + + if (strcasecmp(status.c_str(), "PREAUTH") == 0) + { + status="OK"; + preauthenticated=true; + } + + if (imapAccount.ok(status)) + { + if (!capability_sent) + { + static const char courierimap[]="Double Precision, Inc."; + static const char cyrusimap[]="Cyrus IMAP4"; + + if (search(b, e, courierimap, + courierimap+sizeof(courierimap)-1) + != e) + { + imapAccount.setCapability(LIBMAIL_CHEAPSTATUS); + imapAccount.servertype=IMAP_COURIER; + imapAccount.serverdescr="Courier-IMAP server"; + } + else if (search(b, e, cyrusimap, + cyrusimap+sizeof(cyrusimap)-1) + != e) + { + imapAccount.setCapability(LIBMAIL_CHEAPSTATUS); + imapAccount.servertype=IMAP_CYRUS; + imapAccount.serverdescr="Cyrus IMAP server"; + } + + imapAccount.smap=false; + + while (b != e && + unicode_isspace((unsigned char)*b)) + ++b; + + if (*b == '[') + { + string::iterator c= ++b; + + while (c != e) + { + if (*c == ']') + break; + c++; + } + + string caps=string(b, c); + + b=caps.begin(); + e=caps.end(); + + string w=imapAccount.get_word(&b, &e); + + upper(w); + + if (w == "CAPABILITY") + while ((w=imapAccount + .get_word(&b, &e)) + .size() > 0) + { + upper(w); + if (w == "SMAP1") + imapAccount. + smap= + true; + } + } + if (myLoginInfo.options.count("imap") > 0) + imapAccount.smap=false; + + capability_sent=1; + imapAccount.imapcmd((imapAccount.smap + ? "\\SMAP1": + "CAPABILITY"), + "CAPABILITY\r\n"); + } + return p; + } + + upper(status); + if (status == "CAPABILITY") + { + imapAccount.clearCapabilities(); + while ((status=imapAccount.get_word(&b, &e)).length() > 0) + imapAccount.setCapability(status); + return p; + } + error(imapAccount, buffer_cpy); + return p; + } + + string w=imapAccount.get_cmdreply(&b, &e); + + if (imapAccount.smap) + { + if (w == "+OK") + { + completed=1; + } + else if (w == "-ERR") + { + error(imapAccount, string(b, e)); + return p; + } + } + else if (w == "CAPABILITY") + { + w=imapAccount.get_word(&b, &e); + + if (!imapAccount.ok(w)) + { + error(imapAccount, buffer_cpy); + return p; + } + + if (!imapAccount.hasCapability("IMAP4REV1")) + { + error(imapAccount, "Server does not support IMAP4rev1."); + return p; + } + + completed=1; + } + + if (completed) + { + mail::loginInfo l=myLoginInfo; + + bool preauthArg=preauthenticated; + + imapAccount.uninstallHandler(this); + imapAccount + .installForegroundTask(new + mail::imapLoginHandler(l, + preauthArg) + ); + return (p); + } + return 0; +} + +////////////////////////////////////////////////////////////////////////// +// +// Check the LOGIN status + +mail::imapLoginHandler::imapLoginHandler(mail::loginInfo myInfo, + bool preauthenticatedArg) + : myLoginInfo(myInfo), completed(0), + preauthenticated(preauthenticatedArg) +{ +} + +mail::imapLoginHandler::~imapLoginHandler() +{ + if (!completed) + { + myLoginInfo.callbackPtr->fail("Failed connecting to server."); + } +} + +const char *mail::imapLoginHandler::getName() { return "LOGIN"; } + +void mail::imapLoginHandler::timedOut(const char *errmsg) +{ + completed=1; + callbackTimedOut(*myLoginInfo.callbackPtr, errmsg); +} + +////////////////////////////////////////////////////////////////////////// +// +// Are we ready to go? + +void mail::imapLoginHandler::installed(mail::imap &imapAccount) +{ + next_cram_func=0; + + currentCmd="STARTTLS"; + +#if HAVE_LIBCOURIERTLS + + if (!myimap->socketEncrypted() && + myimap->hasCapability("STARTTLS") && + myLoginInfo.options.count("notls") == 0) + { + myimap->imapcmd((myimap->smap ? "\\SMAP1":"STARTTLS"), + "STARTTLS\r\n"); + return; + } + +#endif + installedtls(imapAccount); +} + +void mail::imapLoginHandler::installedtls(mail::imap &imapAccount) +{ + if (!preauthenticated && imapAccount.hasCapability("AUTH=EXTERNAL")) + { + currentCmd="LOGINEXT"; + imapAccount.imapcmd(imapAccount.smap ? "\\SMAP1":"LOGINEXT", + "AUTHENTICATE EXTERNAL =\r\n"); + return; + } + installedtlsnonext(imapAccount); +} + +void mail::imapLoginHandler::installedtlsnonext(mail::imap &imapAccount) +{ + if (!preauthenticated && myLoginInfo.loginCallbackFunc) + { + loginMethod="LOGIN"; + + if (myLoginInfo.uid.size() == 0) + { + loginCallbackFunc= &mail::imapLoginHandler + ::loginCallbackUid; + + currentCallback=myLoginInfo.loginCallbackFunc; + currentCallback->target=this; + currentCallback->getUserid(); + return; + } + + if (myLoginInfo.pwd.size() == 0) + { + loginCallbackUid(myLoginInfo.uid); + return; + } + } + + loginCallbackPwd(myLoginInfo.pwd); +} + +void mail::imapLoginHandler::loginInfoCallback(std::string arg) +{ + currentCallback=NULL; + (this->*loginCallbackFunc)(arg); +} + +void mail::imapLoginHandler::loginInfoCallbackCancel() +{ + currentCallback=NULL; + failcmd(*myimap, "Login cancelled."); +} + +void mail::imapLoginHandler::loginCallbackUid(std::string arg) +{ + myLoginInfo.uid=arg; + + if (myLoginInfo.pwd.size() == 0) + { + loginCallbackFunc= &mail::imapLoginHandler + ::loginCallbackPwd; + + currentCallback=myLoginInfo.loginCallbackFunc; + currentCallback->target=this; + currentCallback->getPassword(); + return; + } + + loginCallbackPwd(myLoginInfo.pwd); +} + +void mail::imapLoginHandler::loginCallbackPwd(std::string arg) +{ + myLoginInfo.pwd=arg; + + logincram(*myimap, "Login FAILED"); +} + + +////////////////////////////////////////////////////////////////////////////// +// +// Go! + +void mail::imapLoginHandler::logincram(mail::imap &imapAccount, string errmsg) +{ + if (preauthenticated) + { + // Fake a succesfull login :-) + + currentCmd="LOGIN"; + if (imapAccount.smap) + imapAccount.imapcmd("", "NOOP\n"); + else + imapAccount.imapcmd("LOGIN", "NOOP\r\n"); + return; + } + + while (mail::imaphmac::hmac_methods[next_cram_func]) + { + next_login_func= &mail::imapLoginHandler::logincram; + + string s=mail::imaphmac::hmac_methods[next_cram_func] + ->getName(); + + if (!imapAccount.hasCapability("AUTH=CRAM-" + s)) + { + next_cram_func++; + continue; + } + + imapAccount.installBackgroundTask(new + mail::imapCRAMHandler(myLoginInfo, + *this, + *mail::imaphmac + :: + hmac_methods + [next_cram_func++])); + + currentCmd="LOGIN"; + loginMethod="CRAM-" + s; + imapAccount.imapcmd(imapAccount.smap ? "\\SMAP1":"LOGIN", + "AUTHENTICATE CRAM-" + s + "\r\n"); + return; + } + if (myLoginInfo.options.count("cram") > 0) + { + failcmd(imapAccount, errmsg); + return; + } + + logincmd(imapAccount, errmsg); +} + +void mail::imapLoginHandler::logincmd(mail::imap &imapAccount, string errmsg) +{ + next_login_func= &mail::imapLoginHandler::failcmd; + loginMethod="LOGIN"; + +#if 0 + imapAccount.imapcmd("LOGIN","LOGIN " + + imapAccount.quoteSimple(myLoginInfo.uid) + " " + + imapAccount.quoteSimple(myLoginInfo.pwd) + "\r\n"); +#else + + currentCmd="LOGIN"; + + if (imapAccount.smap) + { + imapAccount.imapcmd("\\SMAP1", "LOGIN " + + imapAccount.quoteSMAP(myLoginInfo.uid) + + " " + + imapAccount.quoteSMAP(myLoginInfo.pwd) + + "\n"); + return; + } + + ostringstream uidLen; + + uidLen << myLoginInfo.uid.size(); + + ostringstream pwdLen; + + pwdLen << myLoginInfo.pwd.size(); + + cmds.insert(cmds.end(), myLoginInfo.uid + " {" + pwdLen.str() + + "}\r\n"); + cmds.insert(cmds.end(), myLoginInfo.pwd + "\r\n"); + + imapAccount.imapcmd("LOGIN", "LOGIN {" + uidLen.str() + "}\r\n"); +#endif +} + +bool mail::imapLoginHandler::continuationRequest(mail::imap &imapAccount, string msg) +{ + list<string>::iterator b=cmds.begin(); + + if (b == cmds.end()) + return false; + + string s= *b; + + cmds.erase(b); + + imapAccount.socketWrite(s); + return true; +} + +void mail::imapLoginHandler::failcmd(mail::imap &imapAccount, string message) +{ + completed=1; + mail::callback *callback=myLoginInfo.callbackPtr; + + message=loginMethod + ": " + message; + imapAccount.uninstallHandler(this); + callback->fail(message); +} + +bool mail::imapLoginHandler::untaggedMessage(mail::imap &imapAccount, string name) +{ + if (name == "NAMESPACE") + { + imapAccount.installBackgroundTask(new mail::imapNAMESPACE); + return true; + } + + return false; +} + +/////////////////////////////////////////////////////////////////////////// +// +// The "default namespace" for SMAP account is a top-level LIST + +LIBMAIL_START + +class smapNamespace : public mail::callback, public mail::callback::folderList{ + + ptr<imap> myImap; + + mail::callback *origCallback; + +public: + smapNamespace(imap *imapPtr, mail::callback *origCallbackArg); + ~smapNamespace(); + void success(string); + void fail(string); + void success(const vector<const mail::folder *> &folders); + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); +}; + +LIBMAIL_END + +mail::smapNamespace::smapNamespace(mail::imap *imapPtr, + mail::callback *origCallbackArg) + : myImap(imapPtr), origCallback(origCallbackArg) +{ +} + +mail::smapNamespace::~smapNamespace() +{ +} + +void mail::smapNamespace::success(string s) +{ + mail::callback * volatile p=origCallback; + + delete this; + + p->success(s); +} + +void mail::smapNamespace::fail(string s) +{ + mail::callback * volatile p=origCallback; + + delete this; + + p->fail(s); +} + +void mail::smapNamespace::success(const vector<const mail::folder *> &folders) +{ + if (!myImap.isDestroyed()) + { + vector<const mail::folder *>::const_iterator + b=folders.begin(), e=folders.end(); + + while (b != e) + { + mail::imapFolder f( *myImap, (*b)->getPath(), "", + (*b)->getName(), 0); + + f.hasMessages( (*b)->hasMessages()); + f.hasSubFolders( (*b)->hasSubFolders()); + + myImap->namespaces.push_back(f); + b++; + } + + // Install a dummy namespace entry that points back to the + // root. + + mail::imapFolder f(*myImap, "", "", + "Other Folders", + 0); + + f.hasMessages(false); + f.hasSubFolders(true); + myImap->namespaces.push_back(f); + } +} + +void mail::smapNamespace::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ +} + +bool mail::imapLoginHandler::taggedMessage(mail::imap &imapAccount, string name, + string message, + bool okfail, + string errmsg) +{ + string w; + + if (imapAccount.smap) + name=currentCmd; + + if (name == "LOGINEXT") + { + if (!okfail) + { + installedtlsnonext(imapAccount); + return true; + } + name="LOGIN"; + } + + if (!okfail) + { + if (name == "LOGIN") + { + (this->*next_login_func)(imapAccount, errmsg); + return true; + } + + failcmd(imapAccount, message); + return true; + } + +#if HAVE_LIBCOURIERTLS + + if (name == "STARTTLS") + { + completed=1; + + if (!imapAccount.socketBeginEncryption(myLoginInfo)) + return true; + + completed=0; + currentCmd="TLSCAPABILITY"; + + imapAccount.imapcmd(imapAccount.smap ? + "\\SMAP1":"TLSCAPABILITY", "CAPABILITY\r\n"); + return true; + } + +#endif + + if (name == "LOGIN") + { + if (imapAccount.smap) + name="CAPABILITY"; + else + { + imapAccount.imapcmd("CAPABILITY", "CAPABILITY\r\n"); + return true; + } + } + + if (name == "TLSCAPABILITY") + { + installedtls(imapAccount); + return true; + } + + if (name == "CAPABILITY") + { + imapAccount.namespaces.clear(); + + if (imapAccount.smap) + { + // SMAP: LIST top level folders, that's our namespace + + smapNamespace *cb= + new smapNamespace(&imapAccount, + myLoginInfo.callbackPtr); + + if (!cb) + { + mail::callback *callback= + myLoginInfo.callbackPtr; + completed=1; + imapAccount.uninstallHandler(this); + + callback->fail(strerror(errno)); + return true; + } + + try { + imapAccount.readSubFolders("", *cb, *cb); + } catch (...) { + delete cb; + + mail::callback *callback= + myLoginInfo.callbackPtr; + completed=1; + imapAccount.uninstallHandler(this); + + callback->fail(strerror(errno)); + return true; + } + + completed=1; + imapAccount.uninstallHandler(this); + } + else + { + imapAccount.namespaces + .push_back(mail::imapFolder(imapAccount, + "INBOX", "", + "INBOX", -1)); + if (imapAccount.hasCapability("NAMESPACE")) + imapAccount.imapcmd("NAMESPACE", + "NAMESPACE\r\n"); + else + { + mail::imapFolder f(imapAccount, "", + "", + "Folders", + 0); + + f.hasMessages(false); + f.hasSubFolders(true); + imapAccount.namespaces.push_back(f); + + completed=1; + mail::callback *callback= + myLoginInfo.callbackPtr; + imapAccount.uninstallHandler(this); + callback->success(message); + } + } + return (true); + } + + if (name == "NAMESPACE") + { + vector <mail::imapFolder>::iterator b, e, save; + + b=imapAccount.namespaces.begin(); + e=imapAccount.namespaces.end(); + + while (b != e) + { + save=b++; + + if (b == e || b->getType() != save->getType()) + switch (save->getType()) { + case 0: + save->setName("Folders"); + break; + case 1: + save->setName("Other folders"); + break; + case 2: + save->setName("Shared folders"); + break; + } + + while (b != e && b->getType() == save->getType()) + save=b++; + } + completed=1; + + mail::callback *callback=myLoginInfo.callbackPtr; + + errmsg = loginMethod + ": " + errmsg; + + if (imapAccount.socketEncrypted()) + errmsg="SSL " + errmsg; + + if (myLoginInfo.options.count("faststatus") > 0) + imapAccount.setCapability(LIBMAIL_CHEAPSTATUS); + else if (myLoginInfo.options.count("slowstatus") == 0) + { + b=imapAccount.namespaces.begin(); + e=imapAccount.namespaces.end(); + + while (b != e) + { + if (b->getPath() == "#news.") + break; + b++; + } + + if (b != e && imapAccount.hasCapability("SCAN")) + { + imapAccount.servertype=IMAP_UWIMAP; + imapAccount.serverdescr="UW-IMAP server"; + } + else + { + imapAccount.setCapability(LIBMAIL_CHEAPSTATUS); + } + } + + imapAccount.uninstallHandler(this); + + callback->success(errmsg); + return (true); + } + + return (false); +} + +////////////////////////////////////////////////////////////////////////// +// +// CRAM handling + +mail::imapCRAMHandler::imapCRAMHandler(const mail::loginInfo &myLoginInfoArg, + mail::imapLoginHandler + &loginHandlerArg, + const mail::imaphmac &hmacArg) + : myLoginInfo(myLoginInfoArg), + loginHandler(loginHandlerArg), + hmac(hmacArg) +{ +} + +const char *mail::imapCRAMHandler::getName() +{ + return "* AUTHENTICATE"; +} + + +void mail::imapCRAMHandler::timedOut(const char *errmsg) +{ +} + +mail::imapCRAMHandler::~imapCRAMHandler() +{ +} + +void mail::imapCRAMHandler::installed(mail::imap &imapAccount) +{ +} + +bool mail::imapCRAMHandler::untaggedMessage(mail::imap &imapAccount, string name) +{ + return false; +} + +bool mail::imapCRAMHandler::taggedMessage(mail::imap &imapAccount, + string name, + string message, + bool okfail, string errmsg) +{ + bool rc=loginHandler + .taggedMessage(imapAccount, name, message, okfail, errmsg); + imapAccount.uninstallHandler(this); + return rc; +} + +bool mail::imapCRAMHandler::continuationRequest(mail::imap &imapAccount, + string message) +{ + message=message.substr(1); + mail::decodebase64str challengeStr; + + challengeStr.decode(message); + + mail::encodebase64str responseStr; + + responseStr.encode(myLoginInfo.uid + " "); + +#if 0 + cerr << "CHALLENGE: key=" << myLoginInfo.pwd + << ", challenge=" << challengeStr.challengeStr << endl; +#endif + + string hmacBinary=hmac(myLoginInfo.pwd, challengeStr.challengeStr); + + string hmacBinaryHex; + + { + ostringstream hexEncode; + + hexEncode << hex; + + string::iterator b=hmacBinary.begin(); + string::iterator e=hmacBinary.end(); + + while (b != e) + hexEncode << setw(2) << setfill('0') + << (int)(unsigned char)*b++; + + hmacBinaryHex=hexEncode.str(); + } + + responseStr.encode(hmacBinaryHex); + responseStr.flush(); + +#if 0 + { + challenge dummyStr; + + dummyStr.decode(responseStr.responseStr); + + cerr << "Response: " << dummyStr.challengeStr << endl; + } +#endif + + imapAccount.imapcmd("", responseStr.responseStr + "\r\n"); + + imapAccount.uninstallHandler(this); + return true; +} + + +////////////////////////////////////////////////////////////////////////// +// +// Process the NAMESPACE reply + +void mail::imapNAMESPACE::process(mail::imap &imapAccount, Token t) +{ + (this->*next_func)(imapAccount, t); +} + +void mail::imapNAMESPACE::start_namespace_list(mail::imap &imapAccount, Token t) +{ + if (namespace_type == 3) + { + if (t != EOL) + error(imapAccount); + return; + } + + if (t == NIL) + { + ++namespace_type; + return; + } + + if (t != '(') + { + error(imapAccount); + return; + } + + next_func= &mail::imapNAMESPACE::start_namespace_entry; +} + +void mail::imapNAMESPACE::start_namespace_entry(mail::imap &imapAccount, Token t) +{ + if (t == ')') + { + ++namespace_type; + next_func= &mail::imapNAMESPACE::start_namespace_list; + return; + } + + if (t != '(') + { + error(imapAccount); + return; + } + + string_cnt=0; + hier=""; + // sep=""; + next_func= &mail::imapNAMESPACE::process_namespace_entry; +} + +void mail::imapNAMESPACE::process_namespace_entry(mail::imap &imapAccount, Token t) +{ + if (t == ')') + { + next_func= &mail::imapNAMESPACE::start_namespace_entry; + return; + } + + if (t == ATOM || t == STRING || t == NIL) + switch (++string_cnt) { + case 1: + hier=t.text; + break; + case 2: + { + mail::imapFolder f(imapAccount, hier, "", // t.text, + hier == "" + ? "Folders":hier, + namespace_type); + + if (t == NIL) + { + f.hasMessages(true); + f.hasSubFolders(false); + } + else + { + f.hasMessages(false); + f.hasSubFolders(true); + } + imapAccount.namespaces.push_back(f); + } + break; + } + else + error(imapAccount); +} diff --git a/libmail/imaplogin.H b/libmail/imaplogin.H new file mode 100644 index 0000000..424cd4f --- /dev/null +++ b/libmail/imaplogin.H @@ -0,0 +1,47 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_imaplogin_H +#define libmail_imaplogin_H + +#include "config.h" +#include "logininfo.H" + +#include "imap.H" + +LIBMAIL_START + +class loginInfo; + +///////////////////////////////////////////////////////////////////////// +// +// Initial handler that waits for a greeting message, and decides what to +// do from there. + +class imapGreetingHandler : public imapHandler { + +private: + mail::loginInfo myLoginInfo; + + const char *getName(); + void timedOut(const char *errmsg); + int capability_sent; + int completed; + + bool preauthenticated; +public: + imapGreetingHandler(mail::loginInfo loginInfo); + ~imapGreetingHandler(); + + void installed(imap &); + + int process(imap &imapAccount, std::string &buffer); + + void error(imap &imapAccount, std::string errmsg); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/imaplogout.C b/libmail/imaplogout.C new file mode 100644 index 0000000..b509da2 --- /dev/null +++ b/libmail/imaplogout.C @@ -0,0 +1,143 @@ +/* +** Copyright 2002-2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "imap.H" +#include "misc.H" +#include "imapfolder.H" +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <stdio.h> + +#include <iostream> + +using namespace std; + +LIBMAIL_START + +///////////////////////////////////////////////////////////////////////// +// +// The LOGOUT command + +class imapLogoutHandler : public imapHandler { + + bool eatEverything; + +private: + mail::callback &callback; + bool orderlyShutdown; + + void dologout(imap &imapAccount, mail::callback &callback); + + const char *getName(); + void timedOut(const char *errmsg); + void installed(imap &imapAccount); + +public: + imapLogoutHandler(mail::callback &myCallback); + ~imapLogoutHandler(); + int process(imap &imapAccount, string &buffer); +} ; +LIBMAIL_END + +////////////////////////////////////////////////////////////////////////////// +// +// Logging out. + +void mail::imap::logout(mail::callback &callback) +{ + if (!socketConnected()) + { + callback.success("Already logged out."); + return; + } + + orderlyShutdown=true; + + if (currentFolder) + currentFolder->closeInProgress=true; + + installForegroundTask(new mail::imapLogoutHandler(callback)); +} + +int mail::imapLogoutHandler::process(mail::imap &imapAccount, string &buffer) +{ + if (eatEverything) + return buffer.size(); + + size_t p=buffer.find('\n'); + + if (p == std::string::npos) + { + if (buffer.size() > 16000) + return buffer.size() - 16000; + // SANITY CHECK - DON'T LET HOSTILE SERVER DOS us + return (0); + } + + string buffer_cpy=buffer; + + buffer_cpy.erase(p); + ++p; + + string::iterator b=buffer_cpy.begin(); + string::iterator e=buffer_cpy.end(); + string word=imapAccount.get_cmdreply(&b, &e); + + upper(word); + + if (word == (imapAccount.smap ? "+OK":"LOGOUT")) + dologout(imapAccount, callback); + + return p; +} + +const char *mail::imapLogoutHandler::getName() +{ + return "LOGOUT"; +} + +void mail::imapLogoutHandler::timedOut(const char *errmsg) +{ + callback.success(errmsg ? errmsg:"Timed out."); +} + +void mail::imapLogoutHandler::installed(mail::imap &imapAccount) +{ + if (imapAccount.smap) + imapAccount.imapcmd("", "LOGOUT\n"); + else + imapAccount.imapcmd("LOGOUT", "LOGOUT\r\n"); +} + +mail::imapLogoutHandler::imapLogoutHandler(mail::callback &myCallback) + : eatEverything(false), + callback(myCallback), orderlyShutdown(false) +{ +} + +mail::imapLogoutHandler::~imapLogoutHandler() +{ + if (!orderlyShutdown) + callback.success("Logged out."); +} + +#include <fstream> + +void mail::imapLogoutHandler::dologout(mail::imap &imapAccount, + mail::callback &c) +{ + if (!imapAccount.socketEndEncryption()) + { + orderlyShutdown=true; + imapAccount.uninstallHandler(this); + c.success("Logged out."); + } + else + { + eatEverything=true; + } +} diff --git a/libmail/imapparsefmt.C b/libmail/imapparsefmt.C new file mode 100644 index 0000000..fb26cba --- /dev/null +++ b/libmail/imapparsefmt.C @@ -0,0 +1,152 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "imap.H" +#include "imapparsefmt.H" +#include <stdlib.h> +#include <string.h> +#include <ctype.h> + +using namespace std; + +///////////////////////////////////////////////////////////////////////// +// +// Parse a structured IMAP reply + +mail::imapparsefmt::imapparsefmt() + : current(NULL), nil(false), value(""), parent(NULL) +{ +} + +mail::imapparsefmt::imapparsefmt(mail::imapparsefmt *parentArg) + : current(NULL), nil(false), value(""), parent(parentArg) +{ + parent->children.push_back(this); +} + +mail::imapparsefmt::imapparsefmt(const mail::imapparsefmt &cpy) + : current(NULL), nil(false), value(""), parent(NULL) +{ + try { + (*this)=cpy; + } catch (...) + { + destroy(); + children.clear(); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +mail::imapparsefmt::~imapparsefmt() +{ + destroy(); +} + +mail::imapparsefmt &mail::imapparsefmt +::operator=(const mail::imapparsefmt &cpy) +{ + destroy(); + children.clear(); + + current=cpy.current; + nil=cpy.nil; + value=cpy.value; + parent=cpy.parent; + + vector<mail::imapparsefmt *>::const_iterator b=cpy.children.begin(), + e=cpy.children.end(); + + while (b != e) + { + mail::imapparsefmt *ptr=new mail::imapparsefmt(**b); + + if (!ptr) + LIBMAIL_THROW("Out of memory."); + + try { + ptr->parent=this; + children.push_back(ptr); + } catch (...) + { + delete ptr; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + b++; + } + return *this; +} + +void mail::imapparsefmt::destroy() +{ + vector<mail::imapparsefmt *>::iterator b=children.begin(), + e=children.end(); + + while (b != e) + { + delete *b; + b++; + } +} + +bool mail::imapparsefmt::process(mail::imap &imapAccount, + mail::imapHandlerStructured::Token &t, + bool &parse_error) +{ + parse_error=false; + if (current == NULL) // Start of tree. + { + if (t == mail::imapHandlerStructured::NIL) + { + nil=1; + return true; + } + + if (t == mail::imapHandlerStructured::ATOM || + t == mail::imapHandlerStructured::STRING) + { + value=t.text; + return true; + } + if (t != '(') + { + parse_error=true; + return true; + } + + current=this; + return false; + } + + if (t == ')') + { + current=current->parent; + return (current == NULL); + } + + mail::imapparsefmt *child=new mail::imapparsefmt(current); + + if (t == mail::imapHandlerStructured::NIL) + { + child->nil=1; + return false; + } + + if (t == mail::imapHandlerStructured::ATOM || + t == mail::imapHandlerStructured::STRING) + { + child->value=t.text; + return false; + } + + if (t != '(') + { + parse_error=1; + return true; + } + + current=child; + return false; +} diff --git a/libmail/imapparsefmt.H b/libmail/imapparsefmt.H new file mode 100644 index 0000000..0d529ff --- /dev/null +++ b/libmail/imapparsefmt.H @@ -0,0 +1,63 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_imapparsefmt_H +#define libmail_imapparsefmt_H + +#include "libmail_config.h" +#include "mail.H" +#include "imaphandler.H" +#include <vector> + +LIBMAIL_START + +class imap; +class imapHandlerStructured; + +///////////////////////////////////////////////////////////////////// +// +// A helper class for parsing complicated IMAP replies. +// +// Intended to be used by a imapHandlerStructured subclass. +// +// Have the subclass construct this object, then repeatedly call +// process(), as it receives tokens from imapHandlerStructured. +// +// Navigate the built tree when process() returns true, looking at children +// and value members. + +class imapparsefmt { + + imapparsefmt *current; + +public: + bool nil; // NIL value + std::string value; // string value + std::vector<imapparsefmt *> children; // Children + imapparsefmt *parent; + + imapparsefmt(); + ~imapparsefmt(); + + void begin() + { + *this=imapparsefmt(); + } + + imapparsefmt &operator=(const imapparsefmt &); + imapparsefmt(const imapparsefmt &); + + bool process(imap &, imapHandlerStructured::Token &, + bool &parse_error); + +private: + imapparsefmt(imapparsefmt *parent); + + void destroy(); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/imapstatushandler.C b/libmail/imapstatushandler.C new file mode 100644 index 0000000..4b1d109 --- /dev/null +++ b/libmail/imapstatushandler.C @@ -0,0 +1,230 @@ +/* +** Copyright 2002-2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "misc.H" +#include "imapstatushandler.H" +#include "imapfolders.H" +#include "smapstatus.H" +#include <stdlib.h> +#include <string.h> +#include <sstream> + +using namespace std; + +// Invoked from mail::imapFolder::readFolderInfo() + +void mail::imap::folderStatus(string path, + mail::callback::folderInfo &callback1, + mail::callback &callback2) +{ + if (!ready(callback2)) + return; + + if (smap) + { + mail::smapSTATUS *s= + new mail::smapSTATUS(path, callback1, callback2); + + installForegroundTask(s); + } + else + { + mail::imapStatusHandler *s= + new mail::imapStatusHandler(callback1, callback2, + path); + installForegroundTask(s); + } +} + +mail::imapStatusHandler +::imapStatusHandler(mail::callback::folderInfo &myCallback, + mail::callback &myCallback2, + string myHier) + : callback1(myCallback), callback2(myCallback2), hier(myHier) +{ +} + +mail::imapStatusHandler::~imapStatusHandler() +{ +} + +const char *mail::imapStatusHandler::getName() +{ + return "STATUS"; +} + + +void mail::imapStatusHandler::timedOut(const char *errmsg) +{ + callback2.fail(errmsg ? errmsg:"Server timed out."); +} + +void mail::imapStatusHandler::installed(mail::imap &imapAccount) +{ + imapAccount.imapcmd("STATUS", "STATUS " + imapAccount.quoteSimple(hier) + + " (MESSAGES UNSEEN)\r\n"); +} + +LIBMAIL_START + +class imapSTATUS : public imapHandlerStructured { + + mail::callback::folderInfo &callback1; + string expectedHier; + + string attrName; + + void (imapSTATUS::*next_func)(imap &, Token); + +public: + imapSTATUS(string hier, mail::callback::folderInfo &callback); + ~imapSTATUS(); + + void installed(imap &imapAccount); +private: + const char *getName(); + void timedOut(const char *errmsg); + + void process(imap &imapAccount, Token t); + + void get_hier_name(imap &imapAccount, Token t); + void get_attr_list(imap &imapAccount, Token t); + void get_attr_name(imap &imapAccount, Token t); + void get_attr_value(imap &imapAccount, Token t); +}; + +LIBMAIL_END + +bool mail::imapStatusHandler::untaggedMessage(mail::imap &imapAccount, string name) +{ + if (name != "STATUS") + return false; + + imapAccount.installBackgroundTask( new mail::imapSTATUS(hier, callback1)); + return true; +} + +bool mail::imapStatusHandler::taggedMessage(mail::imap &imapAccount, string name, + string message, + bool okfail, + string errmsg) +{ + if (name != "STATUS") + return false; + + if (okfail) + callback2.success(errmsg); + else + callback2.fail(errmsg); + imapAccount.uninstallHandler(this); + return true; +} + +/////////////////////////////////////////////////////////////////////////// +// +// * STATUS parsing + +mail::imapSTATUS::imapSTATUS(string hierArg, + mail::callback::folderInfo &callbackArg) + : callback1(callbackArg), expectedHier(hierArg), + next_func( &mail::imapSTATUS::get_hier_name) +{ + callback1.messageCount=0; + callback1.unreadCount=0; +} + +mail::imapSTATUS::~imapSTATUS() +{ +} + +void mail::imapSTATUS::installed(mail::imap &imapAccount) +{ +} + +const char *mail::imapSTATUS::getName() +{ + return "* STATUS"; +} + +void mail::imapSTATUS::timedOut(const char *errmsg) +{ +} + +void mail::imapSTATUS::process(mail::imap &imapAccount, Token t) +{ + (this->*next_func)(imapAccount, t); +} + +void mail::imapSTATUS::get_hier_name(mail::imap &imapAccount, Token t) +{ + if (t == ATOM || t == STRING) + { + next_func= &mail::imapSTATUS::get_attr_list; + + if (t.text != expectedHier) + error(imapAccount); // Sanity check + } + else + error(imapAccount); +} + +void mail::imapSTATUS::get_attr_list(mail::imap &imapAccount, Token t) +{ + if (t == NIL) + { + done(); + callback1.success(); + return; + } + + if (t != '(') + { + error(imapAccount); + return; + } + + next_func= &mail::imapSTATUS::get_attr_name; +} + +void mail::imapSTATUS::get_attr_name(mail::imap &imapAccount, Token t) +{ + if (t == ')') + { + done(); + callback1.success(); + return; + } + + if (t != ATOM && t != STRING) + { + error(imapAccount); + return; + } + + attrName=t.text; + upper(attrName); + next_func= &mail::imapSTATUS::get_attr_value; +} + +void mail::imapSTATUS::get_attr_value(mail::imap &imapAccount, Token t) +{ + if (t != ATOM && t != STRING) + { + error(imapAccount); + return; + } + + size_t num=0; + + istringstream(t.text.c_str()) >> num; + + if (attrName == "MESSAGES") + callback1.messageCount=num; + else if (attrName == "UNSEEN") + callback1.unreadCount=num; + + next_func= &mail::imapSTATUS::get_attr_name; +} diff --git a/libmail/imapstatushandler.H b/libmail/imapstatushandler.H new file mode 100644 index 0000000..b124921 --- /dev/null +++ b/libmail/imapstatushandler.H @@ -0,0 +1,43 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_imapstatushandler_H +#define libmail_imapstatushandler_H + +#include "libmail_config.h" +#include "imaphandler.H" +#include "imapfolders.H" + +//////////////////////////////////////////////////////////////////////// +// The STATUS command + +LIBMAIL_START + +class imapStatusHandler : public imapCommandHandler { + + mail::callback::folderInfo &callback1; + mail::callback &callback2; + +public: + std::string hier; + + imapStatusHandler(mail::callback::folderInfo &myCallback, + mail::callback &myCallback2, + std::string myHier); + ~imapStatusHandler(); + const char *getName(); + void timedOut(const char *errmsg); + + void installed(imap &imapAccount); +private: + + bool untaggedMessage(imap &imapAccount, std::string name); + bool taggedMessage(imap &imapAccount, std::string name, std::string message, + bool okfail, std::string errmsg); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/logininfo.C b/libmail/logininfo.C new file mode 100644 index 0000000..affd088 --- /dev/null +++ b/libmail/logininfo.C @@ -0,0 +1,76 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "logininfo.H" + +/////////////////////////////////////////////////////////////////////////// +// +// mail::imapLoginInfo TLS helper functions + +mail::loginInfo::loginInfo() + : loginCallbackFunc(NULL), + use_ssl(false), callbackPtr(0), tlsInfo(0) +{ +} + +mail::loginInfo::~loginInfo() +{ +} + +/////////////////////////////////////////////////////////////////////////// +// +// The callback object + +mail::loginCallback::loginCallback() : target(NULL) +{ +} + +mail::loginCallback::~loginCallback() +{ +} + +void mail::loginCallback::callback(std::string v) +{ + mail::loginInfo::callbackTarget *t=target; + + target=NULL; + + if (t) + t->loginInfoCallback(v); +} + +void mail::loginCallback::callbackCancel() +{ + mail::loginInfo::callbackTarget *t=target; + + target=NULL; + + if (t) + t->loginInfoCallbackCancel(); +} + +void mail::loginCallback::getUserid() +{ + loginPrompt(USERID, "Userid: "); +} + +void mail::loginCallback::getPassword() +{ + loginPrompt(PASSWORD, "Password: "); +} + +mail::loginInfo::callbackTarget::callbackTarget() + : currentCallback(NULL) +{ +} + +mail::loginInfo::callbackTarget::~callbackTarget() +{ + if (currentCallback) + currentCallback->target=NULL; +} + + diff --git a/libmail/logininfo.H b/libmail/logininfo.H new file mode 100644 index 0000000..8c5549f --- /dev/null +++ b/libmail/logininfo.H @@ -0,0 +1,113 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_logininfo_H +#define libmail_logininfo_H + +#include <string> +#include <map> + +#include "namespace.H" + +LIBMAIL_START + +class callback; + +class fdTLS; + +///////////////////////////////////////////////////////////// +// +// Object for common account login information: +// + +class loginCallback; + +class loginInfo { +public: + + class callbackTarget; + + // Glue to the real callback target: + + class callbackTarget { + + protected: + + loginCallback *currentCallback; + + public: + + callbackTarget(); + virtual ~callbackTarget(); + + virtual void loginInfoCallback(std::string)=0; + virtual void loginInfoCallbackCancel()=0; + }; + + + std::string method; // "imap", "pop3Account", etc... + + std::string server, uid, pwd; // Login id/password + + std::map<std::string, std::string> options; // Other options + + + // FOR INTERNAL USE: + + loginCallback *loginCallbackFunc; + + bool use_ssl; + + class mail::callback *callbackPtr; + class fdTLS *tlsInfo; + // Callback function when login goes through + + loginInfo(); + ~loginInfo(); +}; + +//////////////////////////////////////////////////////////////////////////// +// +// Application supplies a pointer to the following object. + +class loginCallback { + +public: + + enum callbackType { // A hint. + USERID, + PASSWORD + }; + + loginCallback(); + virtual ~loginCallback(); + + // Libmail sets the following pointer, before invoking loginPrompt(): + + loginInfo::callbackTarget *target; // INTERNAL + + + void getUserid(); + void getPassword(); + + virtual void loginPrompt(callbackType cbType, + std::string prompt)=0; + // Applications need to define this. + + + // Applications should not screw arount with target directly, but + // invoke one of the following stubs: + + void callback(std::string); + void callbackCancel(); +}; + + + +LIBMAIL_END + + +#endif + diff --git a/libmail/mail.C b/libmail/mail.C new file mode 100644 index 0000000..81bc499 --- /dev/null +++ b/libmail/mail.C @@ -0,0 +1,820 @@ +/* +** Copyright 2002-2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "mail.H" +#include "sync.H" + +#include "driver.H" +#include "mbox.H" + +#include "runlater.H" +#include "logininfo.H" +#include <iostream> +#include <errno.h> +#include <netinet/in.h> +#include <unistd.h> +#include <pwd.h> +#include <arpa/inet.h> +#include "rfc822/rfc822.h" +#include "unicode/unicode.h" +#include "maildir/maildiraclt.h" +#include "misc.H" +#include <fstream> + +using namespace std; + +list<mail::account *> mail::account::mailaccount_list; + +// +// Initialize mail::folder's default. mail::folder is a superclass for +// mail folder objects. Each mail account type is expected to use a +// subclass for the actual implementation. +// +// Each mail::folder object is associated with a corresponding mail::account +// object. The constructor saves a pointer, and provides methods that +// the subclass can use to determine whether the mail::account object still exists + +mail::folder::folder(mail::account *ptr) : myServer(ptr) +{ +} + +mail::folder::~folder() +{ +} + +// +// The first order of business for any mail::folder method call is to +// determine whether the mail::account object still exists. This method is +// a shortcut for most methods that receive a mail::callback object. +// If the mail::account object was destroyed, callback's fail method is called +// appropriately. + +bool mail::folder::isDestroyed(mail::callback &callback) const +{ + if (myServer.isDestroyed()) + { + callback.fail("Server connection closed."); + return true; + } + + return false; +} + +bool mail::folder::isDestroyed() const +{ + return myServer.isDestroyed(); +} + +string mail::folder::isNewsgroup() const +{ + return ""; +} + +void mail::folder::getMyRights(mail::callback &getCallback, + std::string &rights) const +{ + getCallback.fail("This server does not implement access control lists."); + +} + +void mail::folder::getRights(mail::callback &getCallback, + std::list<std::pair<std::string, std::string> > + &rights) const +{ + rights.clear(); + getCallback.fail("This server does not implement access control lists."); +} + +void mail::folder::setRights(mail::callback &setCallback, + string &errorIdentifier, + vector<std::string> &errorRights, + string identifier, + string rights) const +{ + errorIdentifier=""; + errorRights.clear(); + setCallback.fail("This server does not implement access control lists."); +} + +void mail::folder::delRights(mail::callback &setCallback, + string &errorIdentifier, + vector<std::string> &errorRights, + std::string identifier) const +{ + errorIdentifier=""; + errorRights.clear(); + setCallback.fail("This server does not implement access control lists."); +} + +// +// callback::folder miscellania + +mail::callback::folder::folder() +{ +} + +mail::callback::folder::~folder() +{ +} + +void mail::callback::folder::saveSnapshot(std::string snapshotId) +{ +} + +// mail::callback::folderList miscellania + +mail::callback::folderList::folderList() +{ +} + +mail::callback::folderList::~folderList() +{ +} + +// mail::callback::folderInfo miscellania + +mail::callback::folderInfo::folderInfo() + : messageCount(0), unreadCount(0), fastInfo(false) +{ +} + +mail::callback::folderInfo::~folderInfo() +{ +} + +void mail::callback::folderInfo::success() +{ +} + +// messageInfo miscellania + +mail::messageInfo::messageInfo() +{ + uid=""; + draft=replied=marked=deleted=unread=recent=false; +} + +mail::messageInfo::messageInfo(string s) +{ + uid=""; + draft=replied=marked=deleted=unread=recent=false; + + string::iterator b=s.begin(), e=s.end(); + + while (b != e) + { + switch (*b++) { + case 'D': + draft=true; + break; + case 'R': + replied=true; + break; + case 'M': + marked=true; + break; + case 'T': + deleted=true; + break; + case 'U': + unread=true; + break; + case 'C': + recent=true; + break; + case ':': + uid=string(b, e); + b=e; + break; + } + } +} + +mail::messageInfo::~messageInfo() +{ +} + +mail::messageInfo::operator string() const +{ + string s; + + if (draft) s += "D"; + if (replied) s += "R"; + if (marked) s += "M"; + if (deleted) s += "T"; + if (unread) s += "U"; + if (recent) s += "C"; + + return s + ":" + uid; +} + + +// +// mail::account superclass constructor. The main task at hand is to save +// a reference to the disconnect callback, for later use, and to initialize +// the default character set. +// + +mail::account::account(mail::callback::disconnect &callbackArg) + : disconnect_callback(callbackArg) +{ + my_mailaccount_p=mailaccount_list.insert(mailaccount_list.end(), this); +} + +mail::account::~account() +{ + // We can't just remove (this) from mailaccount_list, because we + // might be iterating mail:;account::process(). + + *my_mailaccount_p=NULL; +} + +////////////////////////////////////////////////////////////////////////////// +// +// Utility functions. +// +////////////////////////////////////////////////////////////////////////////// + +LIBMAIL_START + +string hostname() +{ + char h[512]; + + h[sizeof(h)-1]=0; + + if (gethostname(h, sizeof(h)-1) < 0) + strcpy(h, "localhost"); + return h; +} + +string homedir() +{ + const char *h=getenv("HOME"); + + struct passwd *pw=getpwuid(getuid()); + + if (!pw) + return ""; + + if (h == NULL || !*h) + h=pw->pw_dir; + + return h; +} + +void upper(string &w) +{ + for (string::iterator b=w.begin(), e=w.end(); b != e; ++b) + if (*b >= 'a' && *b <= 'z') + *b += 'A'-'a'; +} +LIBMAIL_END + +static const char x[]="0123456789ABCDEF"; + +static string encword(string w) +{ + string ww=""; + + string::iterator b=w.begin(), e=w.end(), c; + + while (b != e) + { + for (c=b; c != e; c++) + if ( *c == '@' || *c == '/' || *c == '%' || *c == ':') + break; + + ww.append(b, c); + + b=c; + + if (b != e) + { + ww += '%'; + ww += x[ (*b / 16) & 15 ]; + ww += x[ *b & 15]; + b++; + } + } + + return ww; +} + +LIBMAIL_START +string loginUrlEncode(string method, string server, string uid, + string pwd) +{ + return (method + "://" + + (uid.size() > 0 ? encword(uid) + + (pwd.size() > 0 ? ":" + encword(pwd):"") + "@":"") + + server); +} +LIBMAIL_END + +static string decword(string w) +{ + string ww=""; + + string::iterator b=w.begin(), e=w.end(), c; + + while (b != e) + { + for (c=b; c != e; c++) + if ( *c == '%' && e-c > 2) + break; + + ww.append(b, c); + + b=c; + + if (b != e) + { + b++; + + const char *c1= strchr(x, *b++); + const char *c2= strchr(x, *b++); + + char c=0; + + if (c1) c= (c1-x) * 16; + if (c2) c += c2-x; + + ww += c; + } + } + + return ww; +} + +LIBMAIL_START + +// Parse url format: method://uid:pwd@server/option/option... + +bool loginUrlDecode(string url, mail::loginInfo &loginInfo) +{ + size_t n=url.find(':'); + + if (n == std::string::npos) + return false; + + loginInfo.method=url.substr(0, n); + + if (url.size() - n < 3 || url[n+1] != '/' || url[n+2] != '/') + return false; + + string serverStr=url.substr(n+3); + string options=""; + + n=serverStr.find('/'); + + if (n != std::string::npos) + { + options=serverStr.substr(n+1); + serverStr.erase(n); + } + + n=serverStr.rfind('@'); + string uidpwd=""; + + if (n != std::string::npos) + { + uidpwd=serverStr.substr(0, n); + serverStr=serverStr.substr(n+1); + } + loginInfo.server=serverStr; + + n=uidpwd.find(':'); + + string pwd=""; + + if (n != std::string::npos) + { + pwd=uidpwd.substr(n+1); + uidpwd=uidpwd.substr(0, n); + } + + loginInfo.uid=decword(uidpwd); + loginInfo.pwd=decword(pwd); + + while (options.size() > 0) + { + n=options.find('/'); + + string option; + + if (n == std::string::npos) + { + option=options; + options=""; + } + else + { + option=options.substr(0, n); + options=options.substr(n+1); + } + + string optionVal; + + n=option.find('='); + + if (n != std::string::npos) + { + optionVal=option.substr(n+1); + option=option.substr(0, n); + } + + loginInfo.options.insert(make_pair(option, optionVal)); + } + return true; +} + +LIBMAIL_END + +mail::account::openInfo::openInfo() : loginCallbackObj(NULL) +{ +} + +mail::account::openInfo::~openInfo() +{ +} + +mail::account *mail::account::open(mail::account::openInfo oi, + mail::callback &callback, + mail::callback::disconnect + &disconnectCallback) +{ + mail::driver **l=mail::driver::get_driver_list(); + + while (*l) + { + mail::account *a; + + if ( (*(*l)->open_func)(a, oi, callback, disconnectCallback)) + { + if (!a) + callback.fail(strerror(errno)); + return a; + } + + l++; + } + + callback.fail("Invalid mail account URL."); + return NULL; +} + +bool mail::account::isRemoteUrl(std::string url) +{ + mail::driver **l=mail::driver::get_driver_list(); + + while (*l) + { + bool flag; + + if ( (*(*l)->isRemote_func)(url, flag)) + return flag; + + l++; + } + + return false; +} + +// Default implementation of getSendFolder: no such luck. + +mail::folder *mail::account::getSendFolder(const class mail::smtpInfo &info, + const mail::folder *folder, + string &errmsg) +{ + errno=ENOENT; + errmsg="Not implemented."; + return NULL; +} + +// +// The main function. Where everything good happens. +// +// mail::account::process calls each existing mail::account object's handler method. +// +void mail::account::process(vector<pollfd> &fds, int &timeout) +{ + list<mail::account *>::iterator b, e, cur; + + fds.clear(); + b=mailaccount_list.begin(); + e=mailaccount_list.end(); + + while (b != e) + { + // mail::account may destroy itself, so increment the iterator + // before invoking the method + + cur=b; + b++; + + if (*cur == NULL) + mailaccount_list.erase(cur); + else + { + (*cur)->handler(fds, timeout); + } + } + + // Postponed handlers. + mail::runLater::checkRunLater(timeout); +} + +// +// Resume after suspend. +// + +void mail::account::resume() +{ + list<mail::account *>::iterator b, e, cur; + + b=mailaccount_list.begin(); + e=mailaccount_list.end(); + + while (b != e) + { + // mail::account may destroy itself, so increment the iterator + // before invoking the method + + cur=b; + ++b; + + if (*cur) + (*cur)->resumed(); + } +} + +void mail::account::updateNotify(bool enableDisable, callback &callbackArg) +{ + callbackArg.success("OK"); +} + +void mail::account::moveMessagesTo(const std::vector<size_t> &messages, + mail::folder *copyTo, + mail::callback &callback) +{ + generic::genericMoveMessages(this, messages, copyTo, callback); +} + +#if 0 +void mail::account::removeMessages(const std::vector<size_t> &messages, + callback &cb) +{ + cb.fail("TODO"); +} + +#endif + +void mail::account::updateKeywords(const vector<size_t> &messages, + const set<string> &keywords, + bool setOrChange, + // false: set, true: see changeTo + bool changeTo, + mail::callback &cb) +{ + cb.fail("Not implemented"); +} + +void mail::account::updateKeywords(const vector<size_t> &messages, + const vector<string> &keywords, + bool setOrChange, + bool changeTo, + mail::callback &cb) +{ + set<string> s; + + s.insert(keywords.begin(), keywords.end()); + + updateKeywords(messages, s, setOrChange, changeTo, cb); +} + + +void mail::account::updateKeywords(const vector<size_t> &messages, + const list<string> &keywords, + bool setOrChange, + bool changeTo, + mail::callback &cb) +{ + set<string> s; + + s.insert(keywords.begin(), keywords.end()); + + updateKeywords(messages, s, setOrChange, changeTo, cb); +} + +void mail::account::getFolderKeywordInfo(size_t messageNum, + set<string> &keywordRet) +{ + keywordRet.clear(); +} + + + + +// +// Must provide default methods for mail::callback::message, not all +// subclasses must implement every method. +// + +mail::callback::message::message() +{ +} + +mail::callback::message::~message() +{ +} + +void mail::callback::message::messageEnvelopeCallback(size_t n, + const mail::envelope + &env) +{ +} + +void mail::callback::message::messageReferencesCallback(size_t n, + const vector<string> + &references) +{ +} + +void mail::callback::message::messageStructureCallback(size_t n, + const mail::mimestruct + &mstruct) +{ +} + +void mail::callback::message::messageTextCallback(size_t n, string text) +{ +} + +void mail::callback::message +::messageArrivalDateCallback(size_t messageNumber, + time_t datetime) +{ +} + +void mail::callback::message::messageSizeCallback(size_t messageNumber, + unsigned long messagesize) +{ +} + +LIBMAIL_START + +string toutf8(string s) +{ + string u; + + char *p=libmail_u_convert_toutf8(s.c_str(), + unicode_default_chset(), NULL); + + try { + u=p; + free(p); + } catch (...) { + free(p); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + return u; +} + +string fromutf8(string s) +{ + string u; + + char *p=libmail_u_convert_fromutf8(s.c_str(), unicode_default_chset(), + NULL); + + try { + u=p; + free(p); + } catch (...) { + free(p); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + return u; +} +LIBMAIL_END + +// +// Common code for mbox and maildir +// + +string mail::mbox::translatePathCommon(string path, + const char *verbotten, + const char *sep) +{ + string newpath; + + do + { + string component; + + size_t n=path.find('/'); + + if (n == std::string::npos) + { + component=path; + path=""; + } + else + { + component=path.substr(0, n); + path=path.substr(n+1); + } + + vector<unicode_char> ucvec; + + unicode_char *uc; + size_t ucsize; + libmail_u_convert_handle_t h; + + if ((h=libmail_u_convert_tou_init(unicode_default_chset(), + &uc, &ucsize, 1)) == NULL) + { + uc=NULL; + } + else + { + libmail_u_convert(h, component.c_str(), + component.size()); + + if (libmail_u_convert_deinit(h, NULL)) + uc=NULL; + } + + if (!uc) + { + errno=EINVAL; + return ""; + } + + try { + size_t n; + + for (n=0; uc[n]; n++) + ; + + ucvec.insert(ucvec.end(), uc, uc+n); + + for (n=0; n<ucvec.size(); ) + { + if (ucvec[n] == '\\' && n+1 < ucvec.size()) + { + ucvec.erase(ucvec.begin()+n, + ucvec.begin()+n+1); + n++; + continue; + } + if (ucvec[n] == '%') + { + unicode_char ucnum=0; + size_t o=n+1; + + while (o < ucvec.size()) + { + if ((unsigned char)ucvec[o] + != ucvec[o]) + break; + if (!isdigit(ucvec[o])) + break; + + ucnum=ucnum * 10 + + ucvec[o]-'0'; + ++o; + } + + if (o < ucvec.size() && + ucvec[o] == ';') + ++o; + + ucvec[n++]=ucnum; + ucvec.erase(ucvec.begin()+n, + ucvec.begin()+o); + continue; + } + n++; + } + } catch (...) { + free(uc); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + free(uc); + std::string p(mail::iconvert::convert(ucvec, + std::string(unicode_x_imap_modutf7 " ") + + verbotten)); + + + if (newpath.size() > 0) + newpath += sep; + newpath += p; + } while (path.size() > 0); + + return newpath; +} diff --git a/libmail/mail.H b/libmail/mail.H new file mode 100644 index 0000000..281877e --- /dev/null +++ b/libmail/mail.H @@ -0,0 +1,1027 @@ +/* +** Copyright 2003-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_mail_H +#define libmail_mail_H + +#include "libmail_config.h" +#include <time.h> +#include <string> + +#if HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif +#include <sys/types.h> +#if TIME_WITH_SYS_TIME +# include <sys/time.h> +# include <time.h> +#else +# if HAVE_SYS_TIME_H +# include <sys/time.h> +# else +# include <time.h> +# endif +#endif +#if HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <sys/poll.h> + +#include <vector> +#include <list> +#include <set> + +#include "namespace.H" +#include "objectmonitor.H" +#include "misc.H" + +#if LIBMAIL_THROW_DEBUG +#define LIBMAIL_THROW(x) do { fprintf(stderr, "Exception at %s(%d)\n", __FILE__, __LINE__); throw x;} while (0) +#else +#define LIBMAIL_THROW(x) throw x + +#define LIBMAIL_THROW_EMPTY + +#endif + +LIBMAIL_START + +class envelope; +class xenvelope; +class mimestruct; +class addMessage; +class searchParams; +class searchCallback; +class smtpInfo; +class snapshot; +class loginInfo; + +// Encapsulate poll() structures + +typedef struct ::pollfd pollfd; + +// i.e. pollfd.fd, pollfd.events, + +static const int pollin=POLLIN; +static const int pollpri=POLLPRI; +static const int pollout=POLLOUT; +static const int pollerr=POLLERR; +static const int pollhup=POLLHUP; + +static const int pollread= POLLIN | POLLHUP | POLLERR; + +static const int pollwrite= POLLOUT | POLLERR; + + +// +// libmail.a uses a mail::callback object to notify the application when +// the requested operation succeeds. libmail.a's API is completely +// asynchronous. Most operations cannot be completed immediately. Instead, +// the application passes a mail::callback object (or one of its subclasses) +// to a function/method call that makes a particular request. The function +// or a method returns immediately, and the mail::callback's success() or +// fail() method will be called whenever the requested operation completes. +// All processing occurs within mail::account::process(). +// +// It's possible that the requested operation can be completed immediately, +// without blocking for I/O. In which case the function/method call invokes +// the success() or the fail() method prior to returning. + +class callback { +public: + callback() {} + virtual ~callback() {} + virtual void success(std::string message)=0; + virtual void fail(std::string message)=0; + + virtual void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal)=0; + + class disconnect; + class folder; + class folderInfo; + class folderList; + class message; +}; + +class folder; +class messageInfo; +class loginCallback; + +#define MAILCHECKINTERVAL 10 + +/////////////////////////////////////////////////////////////////////////// +// +// A mail::account object represents a single mail account. A mail account is +// defined as a collection of related mail folders. A mail folder may +// contain other mail subfolders, or messages (or both), depending on the +// mail account. +// +// A mail::account object is not instantiated directly, but is instantiated by the +// mail::account::open() function, which instantiated the appropriate subclass +// based on the mail account's URL. mail::account::open() returns a new mail::account +// object. As a rule, the mail::account object is not yet fully initialized; the +// application must wait until the success() callback method is invoked. +// +// Mail folders are arranged into a tree-like hierarchy. The list of folders +// at the topmost hierarchy level is obtained. by the readTopLevelFolders() +// method. + +#define LIBMAIL_SINGLEFOLDER "*LIBMAIL_SINGLEFOLDER" + +#define LIBMAIL_CHEAPSTATUS "*LIBMAIL_CHEAPSTATUS" + +#define LIBMAIL_SERVERTYPE "*LIBMAIL_SERVERTYPE" + +#define LIBMAIL_SERVERDESCR "*LIBMAIL_SERVERDESCR" + +#define LIBMAIL_ACL "ACL" + +class account : public obj { + + // All instantiated mail::account objects: + + static std::list<account *> mailaccount_list; + + std::list<account *>::iterator my_mailaccount_p; + + virtual void resumed()=0; + +public: + static void resume(); +private: + // Handle any pending I/O for this mail::account object. + + virtual void handler(std::vector<pollfd> &fds, int &timeout)=0; + +protected: + + // Disconnect callback object that's passed to the constructor. + + callback::disconnect &disconnect_callback; + +public: + + account(callback::disconnect &disconnect_callback); + // + // disconnect_callback - a callback object. + // disconnect_callback.disconnected() gets called from within + // handler() when the connection to the server is terminated. + // disconnect_callback.servererror() gets called whenever the + // server reports a fatal error which does not result in the + // server connection shutting down. + + virtual ~account(); + + class openInfo { + public: + // + // Information for creating a new mail::account object: + + std::string url; + std::string pwd; + + std::vector<std::string> certificates; // Raw SSL certificates + + std::string extraString; // newsrc filename, etc... + + mail::loginCallback *loginCallbackObj; + + openInfo(); + ~openInfo(); + }; + + + static account *open(openInfo openInfoArg, + callback &callback, + callback::disconnect &disconnectCallback); + // + // Create a new mail::account object. + // + // url - mail account URL. + // pwd - mail account password. May be an empty string for mail + // accounts that do not require passwords (local mail storage). + // callback - The open() function generally returns immediately. + // The app must wait until callback.success() is invoked + // indicating that libmail.a succesfully opened account. + // callback.fail() is invoked if the login failed (at which + // point the mail::account object is automatically destroyed). + // A fatal error within the open function itself, + // (such as an invalid URL or out of memory) results in + // an invocation of callback.fail(), followed by open() + // return a NULL pointer. + // + // disconnect_callback - + // disconnect_callback.disconnected() gets called from + // within handler() when the connection to the server is + // terminated. disconnect_callback.servererror() gets + // called whenever the server reports a fatal error which + // does not result in the server connection shutting down. + // + // The disconnect_callback object may not be destroyed until + // either callback.fail() calls, or the logout() function + // completes (the fail() or the success() method of the + // object passed to mail::account::logout() is invoked). + + + static bool isRemoteUrl(std::string url); + // Return TRUE if url represents a remote server account + // (IMAP, POP3, NNTP, or SMTP) + + static void process(std::vector<pollfd> &fds, int &timeout); + + // + // The process function is where ALL libmail.a processing takes + // place. All other methods in all mail::account objects merely + // initiate a request to perform the given task. The process() + // function must be called periodically in order to process pending + // I/O requests. + // + // The fds and timeout arguments are intended to be passed directly + // to the poll(2) function call. Before calling process() initialize + // fds to an empty vector. 'timeout' should also be initialized + // to a fairly large timeout value, which should generally be the + // timeout before any further application activity takes place. + // + // After process() terminates, it should not be invoked again until + // poll(&fds[0], fds.size(), timeout) indicates that I/O requested + // by the fd array is available, or until the 'tv' timeout expires. + // + // Note that the application should check if fds.size() == 0, which + // indicates that libmail.a does not expect I/O on any file descriptor, + // instead the application needs to wait for a simple timer to expire. + // + // If mail::account::process expects further processing to take place + // earlier than what's indicated by 'timeout', mail::account::process() + // will reduce the indicated timeout (the timeout will never be made + // longer, only shorter). + + static int poll(std::vector<pollfd> &fds, int timeout); + + virtual void logout(callback &callback)=0; + // + // Close the mail account. The application should wait until + // either callback.success() or callback.fail() is called (not much + // of a difference, really, in both cases the mail::account object gets + // automatically destroyed) + + virtual void checkNewMail(callback &callback)=0; + // + // Explicitly check for any new mail if a folder is currently open. + // A call to mail::account::checkNewMail() invokes mail::folderCallback's + // methods to reflect any changes to the currently open folder. + // mail::folderCallback::newMessages() will be invoked if new messages + // were added to the folder. mail::folderCallback::messagesRemoved() + // will be invoked if messages were removed from a folder (perhaps + // by another application doing updateFolderIndexInfo, and + // mail::folderCallback::messageChanged() may be invoked to reflect + // changes to existing messages. + + virtual bool hasCapability(std::string capability)=0; + virtual std::string getCapability(std::string capability)=0; + // + // Indicate whether this mail::account object supports the indicated + // capability. + + virtual class folder *folderFromString(std::string)=0; + // + // The opposite of mail::folder::toString(). Creates a new + // mail::folder object. The mail::folder object is linked to this + // mail::account object, and may only be used as argument to this mail::account + // object's methods. + // Note that the returned object does not imply that the actual + // folder exists, only that if it does exist then the new + // mail::folder object may be used to refer to it. + + virtual void readTopLevelFolders(callback::folderList + &callback1, + callback &callback2)=0; + // Enumerate the topmost mail folders available in this mail account. + // callback1.success() is invoked to enumerate the folders at the + // top level of the folder hierarchy. + + virtual void findFolder(std::string path, + callback::folderList &callback1, + callback &callback2)=0; + // Recreate a folder object. + // + // path - the folder's known path -- can be obtained by invoking + // mail::folder::getPath(). + // + // name - the name of this folder, in the application charset. + + virtual std::string translatePath(std::string path)=0; + // + // Take a "human readable" path, and return its server representation. + // For example: IMAP accounts translate path from the local charset + // to modified-UTF7. + // + // Returns an empty string if the original path was invalid + // (setting errno accordingly). A non-empty return does not guarantee + // an existing folder, merely that the path is valid. + + + + virtual folder *getSendFolder(const smtpInfo &info, + const mail::folder *folder, + std::string &errmsg); + // + // Create a folder object to be used for sending outgoing + // mail. + // + // info - parameters for sending mail + // + // folder - if not NULL, file a CC of the message into this folder + // + // errmsg - if this mail account login cannot be used for sending + // mail, getSendFolder() returns a null ptr, and sets errmsg + // accordingly. + + + enum MessageAttributes { + ARRIVALDATE=1, + MESSAGESIZE=2, + ENVELOPE=4, + MIMESTRUCTURE=8}; + + virtual void readMessageAttributes(const std::vector<size_t> &messages, + MessageAttributes attributes, + callback::message + &callback)=0; + // + // Return metadata about messages in the currently open folder. + // + // messages - a list of messages whose metadata should be returned. + // + // attributes - which attributes to return. A logical OR of the + // enumerated constant. Each constant directly translates to a + // method in the mail::callback::message object. If multiple + // messages are specified the order of messages for whom the + // the attributes are returned is arbitrary. If multiple attrs + // are specified the order in which the attributes are returned is + // arbitrary. + // With multiple messages and attributes, any order is possible. + // It's possible that all attributes for the same message are + // returned before the attributes for the next message are + // returned; or the attribute for all messages are returned before + // returning the next attribute for all messages, etc... + // + // ARRIVALDATE - when the message was added to the + // folder (messageArrivalDateCallback). + // + // MESSAGESIZE - approximate message size (may not be + // exact, may be just a rough estimate -- + // messageSizeCallback). + // + // ENVELOPE - message envelope/thread summary + // (messageEnvelopeCallback, maybe + // messageReferencesCallback). + // + // MIMESTRUCTURE - message's MIME structure + // (messageStructureCallback). + // + + virtual void readMessageContent(const std::vector<size_t> &messages, + bool peek, + enum mail::readMode readType, + callback::message + &callback)=0; + + // + // Return the entire header and/or body content of one or more + // messages. + // + // messages - a list of messages whose contents are returned. + // peek - if true, do not reset the unread flag, if possible. + // justHeader - return the contents of the header portion of the + // message. + // justContents - return the contents of the body portion of each + // message. + // mail::callback::message - the messageTextCallback method of + // this object is invoked for each listed message. The relative + // order of messages is arbitrary, but the requested contents of + // each message are returned in their entirety before returning the + // contents of the next message. + // + // Either justHeader or justContents (or both) must be set. If + // only justHeader is set (justContents is not set), header lines are + // automatically folded. + + virtual void readMessageContent(size_t messageNum, + bool peek, + const class mimestruct &msginfo, + enum mail::readMode readType, + callback::message + &callback)=0; + // + // Return the contents of a MIME section of a message. + // messageNum - the message whose contents are returned. + // peek - do not reset the unread flag, if possible + // msgInfo - the MIME section whose contents to be returned. + // justHeader - return the contents of the header portion of the + // message. + // justContents - return the contents of the body portion of each + // message. + // + // Either justHeader or justContents (or both) must be set. If + // only justHeader is set (justContents is not set), header lines are + // automatically folded. + + virtual void readMessageContentDecoded(size_t messageNum, + bool peek, + const class mimestruct + &msginfo, + callback::message + &callback)=0; + // + // Return the decoded contents of a MIME section. Like + // readMessageContents, except that base64/quoted-printable content + // is automatically decoded, and callback.messageTextCallback() + // gets the raw, decoded, contents. + // + // messageNum - the message whose contents are returned. + // peek - do not reset the unread flag, if possible + // msgInfo - the MIME section whose contents to be returned. + + virtual size_t getFolderIndexSize()=0; + // + // Return the number of messages in the currently opened folders. + // The message number arguments to the previous functions should range + // should range between 0 and one less than the return value from + // getFolderIndexSize() + // + + virtual class messageInfo getFolderIndexInfo(size_t)=0; + // + // Return the mail::messageInfo structure for an indicated message. + // + + virtual void saveFolderIndexInfo(size_t messageNum, + const messageInfo &msgInfo, + callback &callback)=0; + // + // Reflect updated message flags. + // + // messageNum - update flags of this message + // msgInfo - new message flags. + + + virtual void updateFolderIndexFlags(const std::vector<size_t> &messages, + bool doFlip, + bool enableDisable, + const messageInfo &flags, + callback &callback)=0; + // + // Update flags of multiple messages. + // + // messages - list of messages whose flags are to be updated. + // + // doFlip - flip the state of the indicated flags + // + // enableDisable - whether the indicated flags should be set, or + // cleared (ignored if doFlip is set) + // + // flags - which flags should be changed. The flags structure is + // NOT saved for each indicated message, instead the action specified + // by doFlip/enableDisable is applied to each flag set in the flags + // object. + + virtual void updateFolderIndexInfo(callback &)=0; + // + // Remove deleted messages. + + + virtual void removeMessages(const std::vector<size_t> &messages, + callback &cb)=0; + // Remove specified messages + + + + // Update keywords + virtual void updateKeywords(const std::vector<size_t> &messages, + const std::set<std::string> &keywords, + bool setOrChange, + // false: set, true: see changeTo + bool changeTo, + callback &cb); + + // Variations of the above. Implemented in the base class. + + void updateKeywords(const std::vector<size_t> &messages, + const std::vector<std::string> &keywords, + bool setOrChange, + // false: set, true: see changeTo + bool changeTo, + callback &cb); + + void updateKeywords(const std::vector<size_t> &messages, + const std::list<std::string> &keywords, + bool setOrChange, + // false: set, true: see changeTo + bool changeTo, + callback &cb); + + virtual void getFolderKeywordInfo(size_t, std::set<std::string> &); + + // + // Application requests to be notified about folder changes + // immediately, if possible (IMAP IDLE) + // + + virtual void updateNotify(bool enableDisable, callback &callbackArg); + + // + // Update the folder according to the flags on each message. + // Generally, each message with the deleted flag is set gets removed + // from the folder. Additionally, updateFolderIndexInfo() usually + // results in the applicatio notified regarding any changes to the + // folder's contents, via mail::folderCallback's methods. + + virtual void copyMessagesTo(const std::vector<size_t> &messages, + folder *copyTo, + callback &callback)=0; + // + // Copy messages to another folder + // + // messages - which message to copy + // copyTo - a folder from the same, or another mail account. + + + virtual void moveMessagesTo(const std::vector<size_t> &messages, + mail::folder *copyTo, + mail::callback &callback); + // + // Like copyMessagesTo(), except that the messages are moved, not + // copied (invoking mail::folderCallback::messagesRemoved() + // appropriate. + // Subclasses may override. The generic implementation uses + // copyMessagesTo(), followed by removeMessages + + + virtual void searchMessages(const searchParams &searchInfo, + searchCallback &callback)=0; + // + // Search messages in this folder. + // + // searchInfo - specified the search criteria + // + // callback - callback object that receives the list of messages + // that fit the search criteria +}; + +// gcc food + +inline account::MessageAttributes operator| (account::MessageAttributes a, + account::MessageAttributes b) +{ + return (account::MessageAttributes)((int)a | (int)b); +} + +inline account::MessageAttributes operator& (account::MessageAttributes a, + account::MessageAttributes b) +{ + return (account::MessageAttributes)((int)a & (int)b); +} + +inline account::MessageAttributes &operator&= (account::MessageAttributes &a, + account::MessageAttributes b) +{ + return (a=(account::MessageAttributes)((int)a & (int)b)); +} + +inline account::MessageAttributes operator~ (account::MessageAttributes a) +{ + return (account::MessageAttributes)~(int)a; +} + +// +// A folder object represents a folder in a mail account. Each mail::folder +// object is linked to the mail::account object that created it. + +class folder : public obj { + + ptr<mail::account> myServer; + +protected: + bool isDestroyed(callback &callback) const; + bool isDestroyed() const; + + folder(const folder &); // UNDEFINED + folder &operator=(const folder &o) + { + myServer=o.myServer; + return *this; + } + +public: + + // Applications should not use the constructor directly, but instead + // should use other methods in the mail::account and mail::folder objects + // to create instances of mail::folder. Applications can destroy + // mail::folder objects at any time where the object is not an + // argument to an uncompleted request. + + folder(mail::account *); + virtual ~folder(); + + virtual void sameServerAsHelperFunc() const=0; + // + // For internal use. + // + + virtual std::string getName() const=0; + // + // Return the name of the folder, in the application character set. + + virtual std::string getPath() const=0; + // + // Return the folder's path. Applications should consider paths to + // be complete opaque strings, for libmail.a's internal use only. + // The only officially blessed usage for paths is as arguments for + // mail::account::findFolder, and mail::folder::isParentOf(). + + virtual bool hasMessages() const=0; + // + // Return an indication whether this folder can contain message. + + virtual bool hasSubFolders() const=0; + // + // Return an indication whether this folder can contain subfolders. + + virtual std::string isNewsgroup() const; + // + // Returns a non-empty string if this is a newsgroup, with the + // string being the newsgroup name. + + virtual bool isParentOf(std::string path) const=0; + // True if this folder would be considered a parent folder in the + // folder hierarchy. + // + // path - the path of another folder. + + virtual void hasMessages(bool)=0; + virtual void hasSubFolders(bool)=0; + // + // For internal use only + + virtual void readFolderInfo( callback::folderInfo + &callback1, + callback &callback2) const=0; + // Return folder metadata, such as the size of this folder + + + virtual void getParentFolder(callback::folderList &callback1, + callback &callback2) const=0; + + // Return parent folder + + virtual void readSubFolders( callback::folderList &callback1, + callback &callback2) + const=0; + // Enumerate any subfolders in this folder + + virtual mail::addMessage + *addMessage(callback &callback) const=0; + // + // Add a new message to this folder. Returns a mail::addMessage + // object. + // + // callback - the usual callback object. + // + // If addMessage() returns NULL, callback.fail() will be invoked to + // indicate the reason why. Otherwise, the new message's metadata + // should be saved in the mail::addMessage's fields. + // The message's contents must be passed to saveMessageContents() + // (multiple times, if necessary), followed by + // saveMessageContents::go(). If it becomes necessary to abort + // the process, saveMessageContents::fail() will hold the presses, + // invoke callback.fail(), and destroy the mail::addMessage object. + + + virtual void createSubFolder(std::string name, bool isDirectory, + callback::folderList + &callback1, + callback &callback2) const=0; + + // Create a new subfolder. + // + // name - the name of the new subfolder. + // isDirectory - if true, the subfolder is expected to contain + // subfolders. To create a folder that should contain both subfolders + // and messages do this twice, first with isDirectory set to false, + // then with isDirectory set to true. Note that not every mail + // account supports dual-purpose folders. + // callback1 - if succesfully, callback1.success() gets invoked with + // a folder list containing the new folder directory entry. + + virtual void create(bool isDirectory, + callback &callback) const=0; + // + // An alternative path to create a new folder is to use + // mail::account::folderFromString() to create a folder object for a + // folder that doesn't really exist, then use this method to create + // the new folder. + + virtual void destroy(callback &callback, bool destroyDir) + const=0; + // + // The opposite of create() - deletes this folder. destroyDir should + // be set to indicate if this folder contains subfolders or messages. + // For dual-purpose folders, only the folder component represented + // by destroyDir() gets removed. + + virtual void renameFolder(const folder *newParent, std::string newName, + callback::folderList &callback1, + callback &callback2) const=0; + // Rename this folder. + // + // newParent - new parent folder, or NULL if folder should be renamed + // to the top of the hierarcy tree + // newName - new folder name, in application charset + // + + + virtual folder *clone() const=0; + // + // Create another mail::folder object that refers to the same + // folder. + + virtual std::string toString() const=0; + // + // Convert this folder's identity to a single string. Applications + // should consider the string to be a completely opaque string; with + // its only documented purpose is that passing the string to + // mail::account::folderFromString() will create a mail::folder object that + // refers to the same folder that this object refers to. + + virtual void open(callback &openCallback, + snapshot *restoreSnapshot, + callback::folder &folderCallback) const=0; + // + // Open the messages in this folder. Any existing open folder is + // automatically closed. If the open fails, whether an existing folder + // remains open is undefined. + // + // openCallback - the standard callback method whose methods will be + // invoked to indicate whether the operation succeeded or not. + // + // If not NULL, a pointer to a snapshot object that holds a previously + // saved folder index snapshot (see callback::folder::saveSnapshot). + // + // + // folderCallback - the object whose methods will be invoked to notify + // of any changes to the folder's state while it is opened. This + // object must not be destroyed, and must continue to exist until + // either another folder's open request succeeds, or the mail account + // is closed or disconnected. + + + // --------- Access control list support ---------- // + + virtual void getMyRights(callback &getCallback, std::string &rights) + const; + virtual void getRights(callback &getCallback, + std::list<std::pair<std::string, std::string> > + &rights) + const; + + virtual void setRights(callback &setCallback, + std::string &errorIdentifier, + std::vector<std::string> &errorRights, + std::string identifier, + std::string rights) + const; + virtual void delRights(callback &setCallback, + std::string &errorIdentifier, + std::vector<std::string> &errorRights, + std::string identifier) + const; + + + // Helper function for sorting folders. + + class sort { + + bool foldersFirst; + public: + sort(bool foldersFirstArg); + ~sort(); + + bool operator()(const folder *a, const folder *b); + }; +}; + +// +// A mail::callback::disconnect() object is provided for each request to +// open a mail account. +// When the connection to the mail account's server terminates, the object's +// disconnected() method is invoked. If the connection was abnormally +// terminated, the errmsg string will be empty. +// The servererror() method may be invoked at any time to report a critical +// server error that was not severe enough to force the connection to be +// terminated. + +class callback::disconnect { +public: + disconnect() {} + virtual ~disconnect() {} + virtual void disconnected(const char *errmsg)=0; + virtual void servererror(const char *errmsg)=0; +}; + +// +// Callbacks for readMessageContent(): +// + +class callback::message : public callback { +public: + message(); + virtual ~message(); + + virtual void messageEnvelopeCallback(size_t messageNumber, + const envelope + &envelopeArg); + + virtual void messageReferencesCallback(size_t messageNumber, + const std::vector<std::string> + &references); + + virtual void messageArrivalDateCallback(size_t messageNumber, + time_t datetime); + + virtual void messageSizeCallback(size_t messageNumber, + unsigned long size); + + virtual void messageStructureCallback(size_t messageNumber, + const mimestruct + &messageStructure); + virtual void messageTextCallback(size_t n, std::string text); + +}; + +// +// A callback for readTopLevelFolders/readSubFolders: +// This callback object is also used for creating a new folder. A +// mail::callback::folderList object is provided when creating a new folder. +// The success() method is invoked with a vector containing exactly one +// mail::folder object, representing the newly-created folder. +// +// NOTE: The mail::folder objects provided to the success() method are valid +// only until the success method() terminates, and are IMMEDIATELY destroyed +// afterwards. If the callback function needs to preserve a mail::folder +// object (perhaps for the application's main code, later, after it checks +// that this request completed succesfully and needs to do something else to +// the folder) it should use mail::folder::clone() method to create its own +// private copy of the mail::folder object. + +class callback::folderList { +public: + folderList(); + virtual ~folderList(); + virtual void success(const std::vector<const mail::folder *> + &folders)=0; +}; + +///////////////////////////////////////////////////////////////////// +// +// Information about a folder + +class callback::folderInfo { +public: + size_t messageCount; + size_t unreadCount; + + bool fastInfo; + // Initialize to TRUE to only do this if it can be done fast. + + folderInfo(); + virtual ~folderInfo(); + + virtual void success(); +}; + +///////////////////////////////////////////////////////////////////// +// +// A reference to the following object is saved when a folder is opened +// +// The object should be subclassed, and virtual methods implemented. +// +// libmail.a uses the object to notify the application about any changes +// to the contents of the folder. The application should expect any of +// the following methods to be invoked at any time. However, the methods +// are usually invoked by mail::account::saveFolderIndexInfo(), +// mail::account::updateFolderIndexFlags(), mail::account::updateFolderIndexInfo(), +// and mail::account::checkNewMail(). Additionally, if the 'peek' argument to +// mail::account::readMessageContents(), and mail::account::readMessageContentsDecoded() is +// false, mail::folderCallback::messageChanged() will be invoked to indicate +// that the message's unread flag has been reset. +// +// However, the application must be prepared to deal with any method being +// invoked at any time, in the event that the mail account allows the same +// folder to be opened by multiple applications, and another applications +// makes changes to the folder. + +class callback::folder { +public: + folder(); + virtual ~folder(); + + virtual void newMessages()=0; + // + // New messages have been added to the folder. The application + // should use mail::account::getFolderIndexSize() to obtain the current + // folder size, and compare it with the previously known folder + // size in order to determine newly-added messages. + + virtual void messagesRemoved(std::vector< std::pair<size_t, size_t> > + &)=0; + // + // Messages were removed from the folder. messagesRemoved() receives + // a reference to an array of first-last pairs. Each first-last + // pair specifies a range of messages removed from the folder index. + // <3, 5> indicates that messages 3, 4, and 5 were removed, + // <3, 3> indicates that message #3 was removed. + // + // The array is sorted in numerically increasing order. + + virtual void messageChanged(size_t n)=0; + // + // Message metadata has changed. Use mail::account::getFolderIndexInfo() + // to obtain the updated set of message flags. + + virtual void saveSnapshot(std::string snapshotId); + // Optionally cache the current folder index (getFolderIndexInfo()), + // as snapshot "snapshotId" + +}; + +// +// libmail.a collects the following metadata about each message, when +// a folder is opened. +// The application is notified via mail::folderCallback::messageChanged() +// about any changes to an existing message's metadata. +// NOTE: this includes changes initiated by the application itself. +// For example, mail::folderCallback::messageChanged() will usually be +// invoked before the mail::account::saveFolderIndexInfo() request completes +// Any changes to the following metadata are +// The folder index. + +class messageInfo { +public: + + std::string uid; + // + // A unique ID is assigned to each message in a folder. Applications + // must consider the uid value to be a completely opaque string. + // The only assumption that applications may make is that no two + // messages will ever have the same uid in the same folder. + // Not all mail accounts support the following flags. Where not, + // libmail.a will simulate the semantics of the flag while the + // mail account is opened. + + bool draft, // This is a draft message + replied, // This message has been replied to + marked, // This message is marked for further processing + deleted, // Marked for deletion (updateFolderIndexInfo() + // should end up removing this message + + unread, // The contents of this message have not been read. + recent; // This is the first time the folder has been opened + // with this message in the folder. + + +#define LIBMAIL_MSGFLAGS \ + do { \ + DOFLAG( FLAG, draft, "\\DRAFT"); \ + DOFLAG( FLAG, replied, "\\ANSWERED"); \ + DOFLAG( FLAG, marked, "\\FLAGGED"); \ + DOFLAG( FLAG, deleted, "\\DELETED"); \ + DOFLAG(NOTFLAG, unread, "\\SEEN"); \ + } while (0) + +#define LIBMAIL_SMAPFLAGS \ + do {\ + DOFLAG( FLAG, draft, "DRAFT");\ + DOFLAG( FLAG, replied, "REPLIED");\ + DOFLAG( FLAG, deleted, "DELETED");\ + DOFLAG( FLAG, marked, "MARKED");\ + DOFLAG(NOTFLAG, unread, "SEEN");\ + } while (0) + + messageInfo(); + + ~messageInfo(); + + operator std::string() const; + messageInfo(std::string); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/maildir.C b/libmail/maildir.C new file mode 100644 index 0000000..4bfc908 --- /dev/null +++ b/libmail/maildir.C @@ -0,0 +1,1718 @@ +/* +** Copyright 2002-2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <ctype.h> +#include <fcntl.h> + +#include "expungelist.H" +#include "misc.H" +#include "maildir.H" +#include "driver.H" +#include "search.H" +#include "copymessage.H" +#include "file.H" + +#include "maildir/config.h" +#include "maildir/maildirmisc.h" +#include "maildir/maildirquota.h" +#include "rfc2045/rfc2045.h" + +#include <fcntl.h> +#include <unistd.h> + +#include <list> +#include <map> + +using namespace std; + +///////////////////////////////////////////////////////////////////////////// + +LIBMAIL_START + +static bool open_maildir(mail::account *&accountRet, + mail::account::openInfo &oi, + mail::callback &callback, + mail::callback::disconnect &disconnectCallback) +{ + if (oi.url.substr(0, 8) != "maildir:") + return false; + + accountRet=new mail::maildir(disconnectCallback, + callback, oi.url.substr(8)); + return true; +} + +static bool maildir_remote(string url, bool &flag) +{ + if (url.substr(0, 8) != "maildir:") + return false; + + flag=false; + return true; +} + +driver maildir_driver= { &open_maildir, &maildir_remote }; + +LIBMAIL_END + +///////////////////////////////////////////////////////////////////////////// + +mail::maildir::maildirMessageInfo::maildirMessageInfo() + : lastKnownFilename(""), changed(false) +{ +} + +mail::maildir::maildirMessageInfo::~maildirMessageInfo() +{ +} + +#define CONSTRUCTOR \ + calledDisconnected(false), \ + sameServerFolderPtr(NULL), \ + folderCallback(NULL), \ + cacheRfcp(NULL), cachefd(-1), \ + watchFolder(NULL), lockFolder(NULL), watchStarting(false) + +mail::maildir::maildir(mail::callback::disconnect &disconnect_callback, + mail::callback &callback, + string pathArg) + : mail::account(disconnect_callback), CONSTRUCTOR +{ + if (init(callback, pathArg)) + callback.success("Mail folders opened"); +} + +mail::maildir::maildir(mail::callback::disconnect &disconnect_callback) + : mail::account(disconnect_callback), CONSTRUCTOR +{ + // Used by pop3maildrop subclass. +} + +bool mail::maildir::init(mail::callback &callback, + string pathArg) +{ + ispop3maildrop=false; + + if (pathArg.size() == 0) + pathArg="Maildir"; + + string h=mail::homedir(); + + if (h.size() == 0) + { + callback.fail("Cannot find my home directory!"); + return false; + } + + if (pathArg[0] != '/') + pathArg= h + "/" + pathArg; + + static const char * const subdirs[]={"tmp","cur","new", 0}; + + int i; + + for (i=0; subdirs[i]; i++) + { + struct stat stat_buf; + + string s= pathArg + "/" + subdirs[i] + "/."; + + if (stat(s.c_str(), &stat_buf) < 0) + { + s="Cannot open maildirAccount: "; + s += strerror(errno); + callback.fail(s); + return false; + } + } + + path=pathArg; + return true; +} + +mail::maildir::~maildir() +{ + updateNotify(false); + + if (lockFolder) + maildirwatch_free(lockFolder); + if (cacheRfcp) + rfc2045_free(cacheRfcp); + if (cachefd >= 0) + close(cachefd); + if (!calledDisconnected) + { + calledDisconnected=true; + disconnect_callback.disconnected(""); + } + + index.clear(); +} + +void mail::maildir::resumed() +{ +} + +void mail::maildir::handler(vector<pollfd> &fds, int &ioTimeout) +{ + int fd; + int rc; + int changed, timeout; + + if (!watchFolder) + return; + + if (watchStarting) + { + rc=maildirwatch_started(&watchFolderContents, &fd); + + if (rc < 0) + { + updateNotify(false); + return; + } + if (rc > 0) + { + watchStarting=false; + updateFolderIndexInfo(NULL, false); + } + if (fd < 0) + return; + + pollfd pfd; + + pfd.fd=fd; + pfd.events=pollread; + pfd.revents=0; + fds.push_back(pfd); + return; + } + + if (maildirwatch_check(&watchFolderContents, &changed, &fd, &timeout) + < 0) + { + updateNotify(false); + return; + } + if (changed) + { + ioTimeout=0; + + MONITOR(mail::maildir); + + updateNotify(false); + updateFolderIndexInfo(NULL, false); + + if (!DESTROYED()) + updateNotify(true); + updateFolderIndexInfo(NULL, false); + return; + } + + timeout *= 1000; + + if (timeout < ioTimeout) + { + ioTimeout=timeout; + } + if (fd < 0) + return; + + pollfd pfd; + + pfd.fd=fd; + pfd.events=pollread; + pfd.revents=0; + fds.push_back(pfd); +} + +void mail::maildir::logout(mail::callback &callback) +{ + updateNotify(false); + + if (!calledDisconnected) + { + calledDisconnected=true; + disconnect_callback.disconnected(""); + } + callback.success("OK"); +} + +// +// Callback collects filenames moved from new to cur on this maildirAccount scan +// +static void recent_callback_func(const char *c, void *vp) +{ + set<string> *setPtr=(set<string> *)vp; + + try { + setPtr->insert( string(c)); + } catch (...) { + } +} + +// Update mail::messageInfo with the current message flags +// (read from the message's filename) +// Returns true if flags have been modified + +bool mail::maildir::updateFlags(const char *filename, + mail::messageInfo &info) +{ + bool flag; + bool changed=false; + +#define DOFLAG(name, NOT, theChar) \ + if ((flag= NOT !!maildir_hasflag(filename, theChar)) != info.name) \ + { info.name=flag; changed=true; } +#define DOFLAG_EMPTY + + DOFLAG(draft, DOFLAG_EMPTY, 'D'); + DOFLAG(replied, DOFLAG_EMPTY, 'R'); + DOFLAG(marked, DOFLAG_EMPTY, 'F'); + DOFLAG(deleted, DOFLAG_EMPTY, 'T'); + DOFLAG(unread, !, 'S'); + +#undef DOFLAG +#undef DOFLAG_EMPTY + + return changed; +} + +// +// Get the filename for message #n +// + +string mail::maildir::getfilename(size_t i) +{ + string n(""); + + if (i >= index.size()) + return n; + + char *fn; + + char *dir=maildir_name2dir(path.c_str(), folderPath.c_str()); + + if (!dir) + return n; + + try { + fn=maildir_filename(dir, NULL, + index[i].lastKnownFilename.c_str()); + free(dir); + } catch (...) { + free(dir); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + if (fn) + try { + n=fn; + free(fn); + } catch (...) { + free(fn); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + return n; +} + +void mail::maildir::checkNewMail(callback &callback) +{ + checkNewMail(&callback); +} + +class mail::maildir::readKeywordHelper { + + struct maildir_kwReadInfo kri; + + mail::maildir *md; + + static struct libmail_kwMessage + **findMessageByFilename(const char *filename, + int autocreate, + size_t *indexNum, + void *voidarg); + + static size_t getMessageCount(void *voidarg); + static struct libmail_kwMessage + **findMessageByIndex(size_t indexNum, + int autocreate, + void *voidarg); + static const char *getMessageFilename(size_t n, void *voidarg); + static struct libmail_kwHashtable *getKeywordHashtable(void *voidarg); + static void updateKeywords(size_t n, struct libmail_kwMessage *kw, + void *voidarg); + + struct libmail_kwMessage**findMessageByFilename(const char *filename, + int autocreate, + size_t *indexNum); + + size_t getMessageCount(); + struct libmail_kwMessage **findMessageByIndex(size_t indexNum, + int autocreate); + const char *getMessageFilename(size_t n); + struct libmail_kwHashtable *getKeywordHashtable(); + void updateKeywords(size_t n, struct libmail_kwMessage *kw); + + map<string, size_t> filenameMap; + + vector<struct libmail_kwMessage *> kwArray; + + +public: + readKeywordHelper(mail::maildir *mdArg); + ~readKeywordHelper(); + + bool go(string maildir, bool &rc); +}; + + +mail::maildir::readKeywordHelper::readKeywordHelper(mail::maildir *mdArg) + : md(mdArg) +{ + size_t n=md->index.size(); + size_t i; + + for (i=0; i<n; i++) + { + string f=md->index[i].lastKnownFilename; + size_t sep=f.rfind(MDIRSEP[0]); + + if (sep != std::string::npos) + f=f.substr(0, sep); + + filenameMap.insert(make_pair(f, i)); + } + + kwArray.resize(n); + for (i=0; i<n; i++) + kwArray[i]=NULL; +} + +bool mail::maildir::readKeywordHelper::go(string maildir, bool &rc) +{ + memset(&kri, 0, sizeof(kri)); + + kri.findMessageByFilename= &readKeywordHelper::findMessageByFilename; + kri.getMessageCount= &readKeywordHelper::getMessageCount; + kri.findMessageByIndex= &readKeywordHelper::findMessageByIndex; + + kri.getMessageFilename= &readKeywordHelper::getMessageFilename; + kri.getKeywordHashtable= &readKeywordHelper::getKeywordHashtable; + kri.updateKeywords=&readKeywordHelper::updateKeywords; + kri.voidarg=this; + + size_t i; + + for (i=0; i<kwArray.size(); i++) + { + if (kwArray[i]) + libmail_kwmDestroy(kwArray[i]); + kwArray[i]=NULL; + } + + char *imap_lock=NULL; + + if (md->lockFolder) + { + int flag; + + imap_lock=maildir_lock(maildir.c_str(), md->lockFolder, + &flag); + + if (!imap_lock) + { + if (!flag) + { + rc=false; + return false; + } + } + } + + if (maildir_kwRead(maildir.c_str(), &kri) < 0) + { + if (imap_lock) + { + unlink(imap_lock); + free(imap_lock); + } + + rc=false; + return false; + } + + if (imap_lock) + { + unlink(imap_lock); + free(imap_lock); + } + + if (kri.tryagain) + return true; + + for (i=0; i<kwArray.size(); i++) + { + if (kwArray[i] == NULL) + { + if (!md->index[i].keywords.empty()) + { + md->index[i].changed=true; + md->index[i].keywords= + mail::keywords::Message(); + } + continue; + } + + if (md->index[i].keywords != kwArray[i]) + { + md->index[i].changed=true; + md->index[i].keywords.replace(kwArray[i]); + kwArray[i]=NULL; + } + } + + rc=true; + return false; +} + +mail::maildir::readKeywordHelper::~readKeywordHelper() +{ + size_t i; + + for (i=0; i<kwArray.size(); i++) + { + if (kwArray[i]) + libmail_kwmDestroy(kwArray[i]); + kwArray[i]=NULL; + } +} + +struct libmail_kwMessage ** +mail::maildir::readKeywordHelper::findMessageByFilename(const char *filename, + int autocreate, + size_t *indexNum, + void *voidarg) +{ + + return ( (mail::maildir::readKeywordHelper *)voidarg) + ->findMessageByFilename(filename, autocreate, indexNum); +} + +size_t mail::maildir::readKeywordHelper::getMessageCount(void *voidarg) +{ + return ( (mail::maildir::readKeywordHelper *)voidarg) + ->getMessageCount(); +} + +struct libmail_kwMessage +**mail::maildir::readKeywordHelper::findMessageByIndex(size_t indexNum, + int autocreate, + void *voidarg) +{ + return ( (mail::maildir::readKeywordHelper *)voidarg) + ->findMessageByIndex(indexNum, autocreate); +} + +const char *mail::maildir::readKeywordHelper::getMessageFilename(size_t n, + void *voidarg) +{ + return ( (mail::maildir::readKeywordHelper *)voidarg) + ->getMessageFilename(n); +} + +struct libmail_kwHashtable * +mail::maildir::readKeywordHelper::getKeywordHashtable(void *voidarg) +{ + return ( (mail::maildir::readKeywordHelper *)voidarg) + ->getKeywordHashtable(); +} + +void mail::maildir::readKeywordHelper::updateKeywords(size_t n, + struct libmail_kwMessage *kw, + void *voidarg) +{ + return ( (mail::maildir::readKeywordHelper *)voidarg) + ->updateKeywords(n, kw); +} + + + + +struct libmail_kwMessage ** +mail::maildir::readKeywordHelper::findMessageByFilename(const char *filename, + int autocreate, + size_t *indexNum) +{ + map<string, size_t>::iterator i=filenameMap.find(filename); + + if (i == filenameMap.end()) + return NULL; + + size_t n=i->second; + + if (indexNum) + *indexNum=n; + + if (kwArray[n] == NULL && autocreate) + if ((kwArray[n]=libmail_kwmCreate()) == NULL) + return NULL; + return &kwArray[n]; +} + +size_t mail::maildir::readKeywordHelper::getMessageCount() +{ + return kwArray.size(); +} + +struct libmail_kwMessage +**mail::maildir::readKeywordHelper::findMessageByIndex(size_t n, + int autocreate) +{ + if (n >= kwArray.size()) + return NULL; + + if (kwArray[n] == NULL && autocreate) + if ((kwArray[n]=libmail_kwmCreate()) == NULL) + return NULL; + return &kwArray[n]; +} + +const char *mail::maildir::readKeywordHelper::getMessageFilename(size_t n) +{ + if (n >= kwArray.size()) + return NULL; + + return md->index[n].lastKnownFilename.c_str(); +} + +struct libmail_kwHashtable * +mail::maildir::readKeywordHelper::getKeywordHashtable() +{ + return &md->keywordHashtable.kwh; +} + +void mail::maildir::readKeywordHelper::updateKeywords(size_t n, + struct libmail_kwMessage *kw) +{ + if (n >= kwArray.size()) + return; + + if (kwArray[n]) + libmail_kwmDestroy(kwArray[n]); + + kwArray[n]=kw; +} + +void mail::maildir::checkNewMail(callback *callback) +{ + if (folderPath.size() == 0) + { + if (callback) + callback->success("OK"); + return; + } + + set<string> recentMessages; + vector<maildirMessageInfo> newIndex; + + string md; + + char *d=maildir_name2dir(path.c_str(), folderPath.c_str()); + + if (!d) + { + callback->fail("Invalid folder"); + return; + } + + try { + md=d; + free(d); + } catch (...) { + free(d); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + // Move messages from new to cur + + maildir_getnew(md.c_str(), NULL, + &recent_callback_func, &recentMessages); + + // Now, rescan the cur directory + + scan(folderPath, newIndex); + + // Now, mark messages from from new to cur as recent + + set<string> existingMessages; + + size_t i, n=index.size(); + + for (i=0; i<n; i++) + existingMessages.insert(index[i].uid); + + bool exists=false; + + for (i=0; i<newIndex.size(); i++) + { + newIndex[i].recent= + recentMessages.count(newIndex[i].lastKnownFilename)>0; + + if (existingMessages.count(newIndex[i].uid) == 0) + { + exists=true; + index.push_back(newIndex[i]); + } + } + + // Invoke callbacks to reflect deleted, or changed, mail + + MONITOR(mail::maildir); + + mail::expungeList removedList; + list<size_t> changedList; + while (n > 0) + { + --n; + + string messageFn=getfilename(n); + + if (messageFn.size() == 0) + { + index.erase(index.begin() + n); + + removedList << n; + continue; + } + else + { + index[n].changed= + updateFlags(messageFn.c_str(), index[n]); + + index[n].lastKnownFilename= + strrchr(messageFn.c_str(), '/')+1; + } + } + + readKeywordHelper rkh(this); + + bool rc; + + while (rkh.go(md, rc)) + ; + + if (!rc) + { + if (callback) + callback->fail(strerror(errno)); + return; + } + + n=index.size(); + + while (n > 0) + { + --n; + + if (index[n].changed) + changedList.insert(changedList.begin(), n); + } + + + removedList >> folderCallback; + + while (!DESTROYED() && !changedList.empty()) + { + list<size_t>::iterator b=changedList.begin(); + + size_t n= *b; + + changedList.erase(b); + + if (folderCallback) + folderCallback->messageChanged(n); + } + + if (!DESTROYED() && exists && folderCallback) + folderCallback->newMessages(); + + if (callback) + callback->success("OK"); +} + + + + +void mail::maildir::genericMarkRead(size_t messageNumber) +{ + if (messageNumber < index.size() && index[messageNumber].unread) + { + index[messageNumber].unread=false; + updateFlags(messageNumber); + } +} + +bool mail::maildir::hasCapability(string capability) +{ + return false; +} + +string mail::maildir::getCapability(string name) +{ + upper(name); + + if (name == LIBMAIL_SERVERTYPE) + { + return "maildir"; + } + + if (name == LIBMAIL_SERVERDESCR) + { + return "Local maildir"; + } + + return ""; +} + +void mail::maildir::readMessageAttributes(const vector<size_t> &messages, + MessageAttributes attributes, + mail::callback::message &callback) +{ + genericAttributes(this, this, messages, attributes, callback); +} + +void mail::maildir::readMessageContent(const vector<size_t> &messages, + bool peek, + enum mail::readMode readType, + mail::callback::message &callback) +{ + genericReadMessageContent(this, this, messages, peek, readType, + callback); +} + +void mail::maildir::readMessageContent(size_t messageNum, + bool peek, + const class mail::mimestruct &msginfo, + enum mail::readMode readType, + mail::callback::message &callback) +{ + genericReadMessageContent(this, this, messageNum, peek, msginfo, + readType, callback); +} + +void mail::maildir::readMessageContentDecoded(size_t messageNum, + bool peek, + const mail::mimestruct &msginfo, + mail::callback::message + &callback) +{ + genericReadMessageContentDecoded(this, this, messageNum, peek, + msginfo, callback); +} + +size_t mail::maildir::getFolderIndexSize() +{ + return index.size(); +} + +mail::messageInfo mail::maildir::getFolderIndexInfo(size_t msgNum) +{ + if (msgNum < index.size()) + return index[msgNum]; + + return mail::messageInfo(); +} + +void mail::maildir::saveFolderIndexInfo(size_t msgNum, + const mail::messageInfo &info, + mail::callback &callback) +{ + if (msgNum >= index.size()) + { + callback.success("OK"); + return; + } + + mail::messageInfo &newFlags=index[msgNum]; + +#define DOFLAG(dummy, field, dummy2) \ + newFlags.field=info.field; + + LIBMAIL_MSGFLAGS; + +#undef DOFLAG + + string errmsg="Message updated"; + + if (!updateFlags(msgNum)) + errmsg="Folder opened in read-only mode."; + + callback.success(errmsg); +} + +bool mail::maildir::updateFlags(size_t msgNum) +{ + bool changed=true; + + string messageFn=getfilename(msgNum); + + if (messageFn.size() > 0) + { + string s(strrchr(messageFn.c_str(), '/')+1); + + size_t i=s.find(MDIRSEP[0]); + + if (i != std::string::npos) + s=s.substr(0, i); + + s += MDIRSEP "2,"; + + s += getMaildirFlags(index[msgNum]); + + const char *fnP=messageFn.c_str(); + + string newName=string(fnP, (const char *)strrchr(fnP, '/')+1) + + s; + + if (newName != messageFn) + { + if (rename(messageFn.c_str(), newName.c_str()) == 0) + { + index[msgNum].lastKnownFilename=s; + } + else + changed=false; + } + + messageFn=getfilename(msgNum); + + if (folderCallback) + folderCallback->messageChanged(msgNum); + } + + return changed; +} + +string mail::maildir::getMaildirFlags(const mail::messageInfo &flags) +{ + string s=""; + + if (flags.draft) + s += "D"; + + if (flags.replied) + s += "R"; + + if (flags.marked) + s += "F"; + + if (!flags.unread) + s += "S"; + + if (flags.deleted) + s += "T"; + return s; +} + +void mail::maildir::updateFolderIndexFlags(const vector<size_t> &messages, + bool doFlip, + bool enableDisable, + const mail::messageInfo &flags, + mail::callback &callback) +{ + string errmsg="Message updated"; + + vector<size_t>::const_iterator b, e; + + b=messages.begin(); + e=messages.end(); + + size_t n=index.size(); + + MONITOR(mail::maildir); + + while (!DESTROYED() && 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 + } + + if (!updateFlags(i)) + errmsg="Folder opened in read-only mode."; + + } + + callback.success(errmsg); +} + +void mail::maildir::removeMessages(const std::vector<size_t> &messages, + callback &cb) +{ + vector<size_t>::const_iterator b=messages.begin(), e=messages.end(); + + while (b != e) + { + size_t n=*b++; + + string messageFn=getfilename(n); + + if (messageFn.size() > 0) + unlink(messageFn.c_str()); + } + updateFolderIndexInfo(&cb, false); +} + +void mail::maildir::updateFolderIndexInfo(mail::callback &callback) +{ + updateFolderIndexInfo(&callback, true); +} + +void mail::maildir::updateFolderIndexInfo(mail::callback *callback, + bool doExpunge) +{ + if (folderPath.size() == 0) + { + if (callback) + callback->success("OK"); + return; + } + + struct stat stat_buf; + + size_t n=index.size(); + + mail::expungeList removedList; + + while (n > 0) + { + --n; + + string messageFn=getfilename(n); + + if (doExpunge) + { + if (!index[n].deleted && messageFn.size() > 0) + continue; + + if (messageFn.size() > 0) + { + unlink(messageFn.c_str()); + messageFn=""; + } + } + + if (messageFn.size() == 0 || + stat(messageFn.c_str(), &stat_buf)) + { + index.erase(index.begin() + n); + + removedList << n; + } + } + + removedList >> folderCallback; + + checkNewMail(callback); +} + +void mail::maildir::copyMessagesTo(const vector<size_t> &messages, + mail::folder *copyTo, + mail::callback &callback) +{ + mail::copyMessages::copy(this, messages, copyTo, callback); +} + +void mail::maildir::searchMessages(const mail::searchParams &searchInfo, + mail::searchCallback &callback) +{ + mail::searchMessages::search(callback, searchInfo, this); +} + +// Open a new folder. + +void mail::maildir::open(string pathStr, mail::callback &callback, + mail::callback::folder &folderCallbackArg) +{ + index.clear(); + updateNotify(false); + + folderCallback=NULL; + if (cacheRfcp) + { + rfc2045_free(cacheRfcp); + cacheRfcp=NULL; + } + + if (cachefd >= 0) + { + close(cachefd); + cachefd=-1; + } + + if (path == "") + { + callback.fail(strerror(errno)); + return; + } + + set<string> recentMessages; + + string md; + + char *d=maildir_name2dir(path.c_str(), pathStr.c_str()); + + if (!d) + { + callback.fail(strerror(errno)); + return; + } + + try { + md=d; + free(d); + } catch (...) { + free(d); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + { + struct stat stat_buf; + + string d=md + "/" KEYWORDDIR; + /* New Courier-IMAP maildirwatch code */ + + mkdir(d.c_str(), 0700); + + if (stat(md.c_str(), &stat_buf) == 0) + chmod(d.c_str(), stat_buf.st_mode & 0777); + } + + maildir_purgetmp(md.c_str()); + maildir_getnew(md.c_str(), NULL, + &recent_callback_func, &recentMessages); + + scan(pathStr, index); + + folderCallback= &folderCallbackArg; + folderPath=pathStr; + + vector<maildirMessageInfo>::iterator b=index.begin(), e=index.end(); + + while (b != e) + { + b->recent=recentMessages.count(b->lastKnownFilename) > 0; + b++; + } + + if (lockFolder) + maildirwatch_free(lockFolder); + + lockFolder=maildirwatch_alloc(md.c_str()); + + readKeywordHelper rkh(this); + + bool rc; + + while (rkh.go(md, rc)) + ; + + callback.success("Mail folder opened"); +} + +/*-------------------------------------------------------------------------*/ + +void mail::maildir::genericMessageRead(string uid, + size_t messageNumber, + bool peek, + mail::readMode readType, + mail::callback::message &callback) +{ + if (!fixMessageNumber(this, uid, messageNumber)) + { + callback.success("OK"); + return; + } + + MONITOR(mail::maildir); + + string messageFn=getfilename(messageNumber); + + if (messageFn.size() == 0) + { + callback.success("OK"); + return; + } + + int fd=maildir_safeopen(messageFn.c_str(), O_RDONLY, 0); + + if (fd < 0) + { + callback.success("OK"); + return; + } + + try { + mail::file f(fd, "r"); + + f.genericMessageRead(this, messageNumber, readType, callback); + + if (ferror(f)) + { + callback.fail(strerror(errno)); + close(fd); + return; + } + + } catch (...) { + close(fd); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + close(fd); + + if (!peek && index[messageNumber].unread) + { + index[messageNumber].unread=false; + saveFolderIndexInfo(messageNumber, + index[messageNumber], + callback); + return; + } + + callback.success("OK"); +} + +void mail::maildir::genericMessageSize(string uid, + size_t messageNumber, + mail::callback::message &callback) +{ + if (!fixMessageNumber(this, uid, messageNumber)) + { + callback.messageSizeCallback(messageNumber, 0); + callback.success("OK"); + return; + } + + string messageFn=getfilename(messageNumber); + + if (messageFn.size() == 0) + { + callback.messageSizeCallback(messageNumber,0); + callback.success("OK"); + return; + } + + unsigned long ul; + + if (maildir_parsequota(messageFn.c_str(), &ul) == 0) + { + callback.messageSizeCallback(messageNumber, ul); + callback.success("OK"); + return; + } + + struct stat stat_buf; + + int rc=stat(messageFn.c_str(), &stat_buf); + + callback.messageSizeCallback(messageNumber, + rc == 0 ? stat_buf.st_size:0); + callback.success("OK"); +} + +void mail::maildir::genericGetMessageFd(string uid, + size_t messageNumber, + bool peek, + int &fdRet, + mail::callback &callback) +{ + struct rfc2045 *dummy; + + genericGetMessageFdStruct(uid, messageNumber, peek, fdRet, dummy, + callback); +} + +void mail::maildir::genericGetMessageStruct(string uid, + size_t messageNumber, + struct rfc2045 *&structRet, + mail::callback &callback) +{ + int dummy; + + genericGetMessageFdStruct(uid, messageNumber, true, dummy, structRet, + callback); +} + +bool mail::maildir::genericCachedUid(string uid) +{ + return uid == cacheUID && cachefd >= 0 && cacheRfcp != 0; +} + +void mail::maildir::genericGetMessageFdStruct(string uid, + size_t messageNumber, + bool peek, + int &fdRet, + struct rfc2045 *&structret, + mail::callback &callback) +{ + if (uid == cacheUID && cachefd >= 0 && cacheRfcp != 0) + { + fdRet=cachefd; + structret=cacheRfcp; + callback.success("OK"); + return; + } + + if (!fixMessageNumber(this, uid, messageNumber)) + { + callback.fail("Message removed on the server"); + return; + } + + if (cacheRfcp) + { + rfc2045_free(cacheRfcp); + cacheRfcp=NULL; + } + + if (cachefd >= 0) + { + close(cachefd); + cachefd= -1; + } + + if (!fixMessageNumber(this, uid, messageNumber)) + { + callback.fail("Message removed on the server"); + return; + } + + string messageFn=getfilename(messageNumber); + + if (messageFn.size() == 0) + { + callback.fail("Message removed on the server"); + return; + } + + int fd=maildir_safeopen(messageFn.c_str(), O_RDONLY, 0); + + struct rfc2045 *rfcp; + + if (fd < 0 || fcntl(fd, F_SETFD, FD_CLOEXEC) < 0 || + (rfcp=rfc2045_alloc()) == NULL) + { + if (fd >= 0) + close(fd); + callback.fail("Message removed on the server"); + return; + } + + fcntl(fd, F_SETFD, FD_CLOEXEC); + + cachefd=fd; + cacheRfcp=rfcp; + cacheUID=""; + + vector<char> buffer; + + buffer.insert(buffer.end(), BUFSIZ, 0); + + int n; + + while ((n=read(fd, &buffer[0], buffer.size())) > 0) + rfc2045_parse(rfcp, &buffer[0], n); + rfc2045_parse_partial(rfcp); + + if (n < 0) + { + callback.fail(strerror(errno)); + return; + } + + fdRet=cachefd; + structret=cacheRfcp; + if (!peek && index[messageNumber].unread) + { + index[messageNumber].unread=false; + saveFolderIndexInfo(messageNumber, index[messageNumber], + callback); + return; + } + callback.success("OK"); +} + +void mail::maildir::updateNotify(bool enableDisable, + mail::callback &callback) +{ + updateNotify(enableDisable); + if (!enableDisable) + updateFolderIndexInfo(&callback, false); + else + callback.success("OK"); +} + +void mail::maildir::updateNotify(bool enableDisable) +{ + if (!enableDisable) + { + if (watchFolder) + { + maildirwatch_end(&watchFolderContents); + maildirwatch_free(watchFolder); + watchFolder=NULL; + } + return; + } + + if (folderPath.size() == 0 || watchFolder) + return; + + char *dir=maildir_name2dir(path.c_str(), folderPath.c_str()); + + if (!dir) + return; + + watchFolder=maildirwatch_alloc(dir); + + free(dir); + + if (!watchFolder) + return; + + if (maildirwatch_start(watchFolder, &watchFolderContents) < 0) + { + maildirwatch_free(watchFolder); + return; + } + watchStarting=true; +} + + +void mail::maildir::getFolderKeywordInfo(size_t n, std::set<std::string> &s) +{ + string messageFn=getfilename(n); + + if (messageFn.size() == 0) + return; + + index[n].keywords.getFlags(s); +} + +void mail::maildir::updateKeywords(const vector<size_t> &messages, + const set<string> &keywords, + bool setOrChange, + bool changeTo, + mail::callback &cb) +{ + bool keepGoing; + + if (folderPath.size() == 0) + { + cb.success("Ok."); + return; + } + + string dir; + + { + char *dirs=maildir_name2dir(path.c_str(), folderPath.c_str()); + if (!dirs) + { + cb.fail(strerror(errno)); + return; + } + + try { + dir=dirs; + free(dirs); + } catch (...) { + free(dirs); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + } + + if (!setOrChange) + { + if (!updateKeywords(dir, + messages, keywords, setOrChange, changeTo, + NULL, NULL)) + { + cb.fail(strerror(errno)); + return; + } + } + else do { + struct libmail_kwGeneric g; + + libmail_kwgInit(&g); + + char *imap_lock=NULL; + + if (lockFolder) + { + int flag; + + imap_lock=maildir_lock(dir.c_str(), lockFolder, + &flag); + + if (!imap_lock) + { + if (!flag) + { + libmail_kwgDestroy(&g); + cb.fail(strerror(errno)); + return; + } + } + } + + try { + if (libmail_kwgReadMaildir(&g, dir.c_str()) < 0) + { + if (imap_lock) + { + unlink(imap_lock); + free(imap_lock); + imap_lock=NULL; + } + + cb.fail(strerror(errno)); + return; + } + + keepGoing=false; + + if (!updateKeywords(dir, + messages, keywords, setOrChange, + changeTo, + &g, &keepGoing)) + { + if (imap_lock) + { + unlink(imap_lock); + free(imap_lock); + imap_lock=NULL; + } + libmail_kwgDestroy(&g); + if (keepGoing) + continue; + + cb.fail(strerror(errno)); + return; + } + if (imap_lock) + { + unlink(imap_lock); + free(imap_lock); + imap_lock=NULL; + } + libmail_kwgDestroy(&g); + } catch (...) { + if (imap_lock) + { + unlink(imap_lock); + free(imap_lock); + imap_lock=NULL; + } + libmail_kwgDestroy(&g); + throw; + } + } while (keepGoing); + + cb.success("Message keywords updated."); +} + + +bool mail::maildir::updateKeywords(string dir, + const vector<size_t> &messages, + const set<string> &keywords, + bool setOrChange, + bool changeTo, + struct libmail_kwGeneric *g, + bool *keepGoing) +{ + mail::keywords::Message m; + + m.setFlags(keywordHashtable, keywords); + + MONITOR(mail::maildir); + + vector<size_t>::const_iterator b=messages.begin(), + e=messages.end(); + + while (!DESTROYED() && b != e) + { + size_t n= *b++; + + string messageFn=getfilename(n); + + if (messageFn.size() == 0) + continue; + + if (!setOrChange) + { + set<string> newFlags; + + m.getFlags(newFlags); + index[n].keywords.setFlags(keywordHashtable, + newFlags); + // Can't copy, because of reference counting. + + char *tmpname, *newname; + + if (maildir_kwSave(dir.c_str(), + index[n].lastKnownFilename + .c_str(), newFlags, + &tmpname, &newname, 0) < 0) + return false; + + int rc=rename(tmpname, newname); + free(tmpname); + free(newname); + + if (rc < 0) + return false; + } + else if (changeTo) + { + struct libmail_kwGenericEntry *origKw= + libmail_kwgFindByName(g, index[n] + .lastKnownFilename + .c_str()); + + set<string>::iterator b=keywords.begin(), + e=keywords.end(); + + while (b != e) + { + if (!index[n].keywords + .addFlag(keywordHashtable, *b)) + { + LIBMAIL_THROW(strerror(errno)); + } + + if (origKw && origKw->keywords && + libmail_kwmSetName(&g->kwHashTable, + origKw->keywords, + b->c_str())) + LIBMAIL_THROW(strerror(errno)); + + ++b; + } + + char *tmpname, *newname; + + set<string> newFlags; + + index[n].keywords.getFlags(newFlags); + + if ((origKw && origKw->keywords ? + maildir_kwSave(dir.c_str(), + index[n].lastKnownFilename + .c_str(), origKw->keywords, + &tmpname, &newname, + 1): + maildir_kwSave(dir.c_str(), + index[n].lastKnownFilename + .c_str(), newFlags, + &tmpname, &newname, + 1)) < 0) + return false; + + int rc=link(tmpname, newname); + unlink(tmpname); + free(tmpname); + free(newname); + + if (rc < 0) + { + if (errno == EEXIST) + *keepGoing=true; + return false; + } + } + else + { + struct libmail_kwGenericEntry *origKw= + libmail_kwgFindByName(g, index[n] + .lastKnownFilename + .c_str()); + + set<string>::iterator b=keywords.begin(), + e=keywords.end(); + + while (b != e) + { + struct libmail_keywordEntry *kef; + + if (!index[n].keywords.remFlag(*b)) + LIBMAIL_THROW(strerror(errno)); + + if (origKw && origKw->keywords && + (kef=libmail_kweFind(&g->kwHashTable, + b->c_str(), + 0)) != NULL) + { + libmail_kwmClear(origKw->keywords, + kef); + } + ++b; + } + char *tmpname, *newname; + set<string> newFlags; + + index[n].keywords.getFlags(newFlags); + + if ((origKw && origKw->keywords + ? maildir_kwSave(dir.c_str(), + index[n].lastKnownFilename + .c_str(), origKw->keywords, + &tmpname, &newname, + 1) + : maildir_kwSave(dir.c_str(), + index[n].lastKnownFilename + .c_str(), newFlags, + &tmpname, &newname, + 1)) < 0) + return false; + + int rc=link(tmpname, newname); + unlink(tmpname); + free(tmpname); + free(newname); + + if (rc < 0) + { + if (errno == EEXIST) + *keepGoing=true; + return false; + } + } + + if (folderCallback) + folderCallback->messageChanged(n); + } + return true; +} diff --git a/libmail/maildir.H b/libmail/maildir.H new file mode 100644 index 0000000..3a7988e --- /dev/null +++ b/libmail/maildir.H @@ -0,0 +1,251 @@ +/* +** Copyright 2002-2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_maildir_H +#define libmail_maildir_H + +#include "libmail_config.h" +#include "mail.H" +#include "generic.H" +#include "unicode/unicode.h" +#include "maildir/maildirwatch.h" +#include "maildir/maildirkeywords.h" + +#include <string> +#include <vector> + +//////////////////////////////////////////////////////////////////////// +// +// A maildirAccount. A notable observation is that the message filenames are +// used directly as uids. + +LIBMAIL_START + +class maildir : public mail::account, public generic { + + bool calledDisconnected; // Disconnected callback has been called. + + void resumed(); + void handler(std::vector<pollfd> &fds, int &timeout); + +public: + std::string path; // Path to the maildirAccount +private: + class folder; +public: + class addmessage; +private: + folder *sameServerFolderPtr; + + +protected: + + mail::callback::folder *folderCallback; + std::string folderPath; // Path to the currently opened folder + + bool ispop3maildrop; // This is a subclass of pop3maildrop + +private: + + // Caching most recently opened message: + std::string cacheUID; + struct rfc2045 *cacheRfcp; + int cachefd; + + + mail::keywords::Hashtable keywordHashtable; // Keyword support + + // The current folder's index. + + class maildirMessageInfo : public mail::messageInfo { + public: + maildirMessageInfo(); + ~maildirMessageInfo(); + + std::string lastKnownFilename; // Message was opened as + bool changed; // Detects when filename has changed. + + mail::keywords::Message keywords; + // Keywords set for a message. + }; + + std::vector<maildirMessageInfo> index; + + class readKeywordHelper; + friend class readKeywordHelper; + + // Get the full path to a message file + + std::string getfilename(size_t index); + + // Rescan the maildirAccount + + bool scan(std::string folderStr, + std::vector<maildirMessageInfo> &index, + bool scanNew=false); + + class indexSort; + + void open(std::string pathStr, mail::callback &callback, + mail::callback::folder &folderCallback); + + static bool updateFlags(const char *fname, mail::messageInfo &info); + + bool updateFlags(size_t msgNum); + + struct maildirwatch *watchFolder; + struct maildirwatch *lockFolder; + struct maildirwatch_contents watchFolderContents; + bool watchStarting; + +public: + static bool maildirmake(std::string subdir, bool isFolder); + static bool maildirdestroy(std::string dir); + + friend class folder; + friend class addmessage; + friend class indexSort; + + maildir(mail::callback::disconnect &disconnect_callback, + mail::callback &callback, + std::string pathArg); + ~maildir(); + + void logout(mail::callback &callback); + void checkNewMail(mail::callback &callback); + +protected: + maildir(mail::callback::disconnect &disconnect_callback); + + bool init(mail::callback &callback, std::string pathArg); + + virtual void checkNewMail(mail::callback *callback); +public: + + bool hasCapability(std::string capability); + std::string getCapability(std::string capability); + + mail::folder *folderFromString(std::string); + void readTopLevelFolders(mail::callback::folderList &callback1, + mail::callback &callback2); + + void findFolder(std::string folder, + mail::callback::folderList &callback1, + mail::callback &callback2); + std::string translatePath(std::string path); + void readMessageAttributes(const std::vector<size_t> &messages, + MessageAttributes attributes, + mail::callback::message &callback); + + void readMessageContent(const std::vector<size_t> &messages, + bool peek, + enum mail::readMode readType, + mail::callback::message &callback); + + void readMessageContent(size_t messageNum, + bool peek, + const class mimestruct &msginfo, + enum mail::readMode readType, + mail::callback::message &callback); + + void readMessageContentDecoded(size_t messageNum, + bool peek, + const mimestruct &msginfo, + mail::callback::message &callback); + + size_t getFolderIndexSize(); + mail::messageInfo getFolderIndexInfo(size_t); + + void saveFolderIndexInfo(size_t, + const mail::messageInfo &, mail::callback &); + + static std::string getMaildirFlags(const mail::messageInfo &); + + void updateFolderIndexFlags(const std::vector<size_t> &messages, + bool doFlip, + bool enableDisable, + const mail::messageInfo &flags, + mail::callback &callback); + + void updateFolderIndexInfo(mail::callback &); + void removeMessages(const std::vector<size_t> &messages, + callback &cb); +private: + void updateFolderIndexInfo(mail::callback *, bool); +public: + + void getFolderKeywordInfo(size_t, std::set<std::string> &); + void updateKeywords(const std::vector<size_t> &messages, + const std::set<std::string> &keywords, + bool setOrChange, + bool changeTo, + callback &cb); +private: + bool updateKeywords(std::string dir, + const std::vector<size_t> &messages, + const std::set<std::string> &keywords, + bool setOrChange, + bool changeTo, + struct libmail_kwGeneric *oldFlags, + bool *keepGoing); +public: + + + void copyMessagesTo(const std::vector<size_t> &messages, + mail::folder *copyTo, + mail::callback &callback); + + void moveMessagesTo(const std::vector<size_t> &messages, + mail::folder *copyTo, + mail::callback &callback); + + void searchMessages(const class mail::searchParams &searchInfo, + class mail::searchCallback &callback); + + void updateNotify(bool enableDisable, callback &callbackArg); + +private: + + void updateNotify(bool enableDisable); + +public: + /* ----- */ + + void genericMessageRead(std::string uid, + size_t messageNumber, + bool peek, + mail::readMode readType, + mail::callback::message &callback); + + void genericMessageSize(std::string uid, + size_t messageNumber, + mail::callback::message &callback); + + void genericGetMessageFd(std::string uid, + size_t messageNumber, + bool peek, + int &fdRet, + mail::callback &callback); + + void genericGetMessageStruct(std::string uid, + size_t messageNumber, + struct rfc2045 *&structRet, + mail::callback &callback); + + bool genericCachedUid(std::string uid); + + void genericGetMessageFdStruct(std::string uid, + size_t messageNumber, + bool peek, + int &fdRet, + struct rfc2045 *&structret, + mail::callback &callback); + + void genericMarkRead(size_t messageNumber); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/maildiradd.C b/libmail/maildiradd.C new file mode 100644 index 0000000..2006105 --- /dev/null +++ b/libmail/maildiradd.C @@ -0,0 +1,230 @@ +/* +** Copyright 2002-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "maildiradd.H" +#include <cstring> + +#include "maildir/config.h" +#include "maildir/maildircreate.h" +#include "maildir/maildirrequota.h" +#include <sys/types.h> +#include <sys/stat.h> + +#include <unistd.h> +#include <errno.h> + +#include <sstream> + +#if HAVE_UTIME_H +#include <utime.h> +#endif +#if TIME_WITH_SYS_TIME +#include <sys/time.h> +#include <time.h> +#else +#if HAVE_SYS_TIME_H +#include <sys/time.h> +#else +#include <time.h> +#endif +#endif + +using namespace std; + +mail::maildir::addmessage::addmessage(mail::maildir *maildirArg, + string folderPathArg, + mail::callback &callbackArg) + : mail::addMessage(maildirArg), + tmpname(""), + newname(""), + folderPath(folderPathArg), + initialized(0), + errmsg(""), + tmpfile(NULL), + callback(&callbackArg) +{ +} + +void mail::maildir::addmessage::initialize() +{ + static unsigned cnt=0; + + initialized=true; + + ostringstream o; + + o << ++cnt; + + string ocnt=o.str(); + + struct maildir_tmpcreate_info createInfo; + + maildir_tmpcreate_init(&createInfo); + + createInfo.maildir=folderPath.c_str(); + createInfo.uniq=ocnt.c_str(); + + while ((tmpfile=maildir_tmpcreate_fp(&createInfo)) == NULL) + { + if (errno != EAGAIN) + { + errmsg=strerror(errno); + return; + } + sleep(3); + } + + try { + tmpname=createInfo.tmpname; + newname=createInfo.newname; + maildir_tmpcreate_free(&createInfo); + } catch (...) { + fclose(tmpfile); + tmpfile=NULL; + unlink(createInfo.tmpname); + maildir_tmpcreate_free(&createInfo); + errmsg=strerror(errno); + } +} + +mail::maildir::addmessage::~addmessage() +{ + if (tmpfile) + { + fclose(tmpfile); + unlink(tmpname.c_str()); + } + + if (callback) + callback->fail("Operation cancelled."); +} + +void mail::maildir::addmessage::saveMessageContents(string s) +{ + if (!initialized) + initialize(); + + if (tmpfile != NULL) + if (fwrite(&s[0], s.size(), 1, tmpfile) != 1) + ; // Ignore gcc warning +} + + +// NOTE: pop3maildrop depends on go() completing immediately. + +void mail::maildir::addmessage::go() +{ + if (!initialized) + initialize(); + + if (!tmpfile) + { + fail(errmsg); + return; + } + + struct stat stat_buf; + + if (ferror(tmpfile) || fflush(tmpfile) || + fstat(fileno(tmpfile), &stat_buf) < 0) + { + fail(strerror(errno)); + return; + } + + // For Courier compatibility, modify the filename to include the + // file size. + + char *p=maildir_requota(newname.c_str(), stat_buf.st_size); + + if (!p) + { + fail(strerror(errno)); + return; + } + + try { + newname=p; + free(p); + } catch (...) { + free(p); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + fclose(tmpfile); + tmpfile=NULL; + + // Now, stick on the flags we want. + + string flags=getMaildirFlags(messageInfo); + + if (flags.size() > 0) + { + newname = newname + MDIRSEP "2," + flags; + memcpy(strrchr(const_cast<char *>(newname.c_str()), '/')-3, + "cur", 3); + // We go into the cur directory, now + } + +#if HAVE_UTIME + if (messageDate) + { + struct utimbuf ub; + + ub.actime=ub.modtime=messageDate; + utime(tmpname.c_str(), &ub); + } +#else +#if HAVE_UTIMES + if (messageDate) + { + struct timeval tv; + + tv.tv_sec=messageDate; + tv.tv_usec=0; + utimes(tmpname.c_str(), &tv); + } +#endif +#endif + + if (maildir_movetmpnew(tmpname.c_str(), newname.c_str()) < 0) + { + fail(strerror(errno)); + return; + } + + try { + if (callback) + { + mail::callback *p=callback; + callback=NULL; + + p->success("OK"); + } + + delete this; + } catch (...) { + delete this; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +void mail::maildir::addmessage::fail(string errmsg) +{ + try { + if (callback) + { + mail::callback *p=callback; + callback=NULL; + + p->fail(errmsg); + } + delete this; + } catch (...) { + delete this; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} diff --git a/libmail/maildiradd.H b/libmail/maildiradd.H new file mode 100644 index 0000000..6e5539b --- /dev/null +++ b/libmail/maildiradd.H @@ -0,0 +1,48 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_maildiradd_H +#define libmail_maildiradd_H + +#include "libmail_config.h" +#include "maildir.H" +#include "addmessage.H" + +#include <stdio.h> + +LIBMAIL_START + +/////////////////////////////////////////////////////////////////////////// +// +// Maildir add message implementation + +class maildir::addmessage : public addMessage { + + std::string tmpname; + std::string newname; + + std::string folderPath; + int initialized; + std::string errmsg; + + void initialize(); + + FILE *tmpfile; + + mail::callback *callback; + +public: + addmessage(maildir *maildirArg, std::string folderPath, + mail::callback &callbackArg); + ~addmessage(); + + void saveMessageContents(std::string); + void go(); + void fail(std::string errmsg); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/maildirfolder.C b/libmail/maildirfolder.C new file mode 100644 index 0000000..9c54e31 --- /dev/null +++ b/libmail/maildirfolder.C @@ -0,0 +1,1003 @@ +/* +** Copyright 2002-2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "maildir/config.h" +#include "maildir/maildirmisc.h" +#include "unicode/unicode.h" +#include "maildirfolder.H" +#include "maildiradd.H" +#include "mbox.H" +#include "misc.H" +#include <list> +#include <algorithm> +#include <errno.h> +#include <string.h> +#include <fcntl.h> + +#include <sys/stat.h> +#include <sys/types.h> +#include <iostream> + +#if HAVE_DIRENT_H +# include <dirent.h> +# define NAMLEN(dirent) strlen((dirent)->d_name) +#else +# define dirent direct +# define NAMLEN(dirent) (dirent)->d_namlen +# if HAVE_SYS_NDIR_H +# include <sys/ndir.h> +# endif +# if HAVE_SYS_DIR_H +# include <sys/dir.h> +# endif +# if HAVE_NDIR_H +# include <ndir.h> +# endif +#endif + +using namespace std; + +mail::maildir::folder::folder(mail::maildir *maildirArg, + string pathArg) + : mail::folder(maildirArg), + maildirAccount(maildirArg), + path(pathArg), + hasMessagesFlag(true), + hasSubfoldersFlag(true) +{ + name=pathArg; + + size_t p=name.rfind('.'); + + if (p != std::string::npos) + name=name.substr(p+1); + + // Convert the name of the folder from modified UTF-7 + // (Courier compatibility) to the current charset. + + char *s=libmail_u_convert_tobuf(name.c_str(), + unicode_x_imap_modutf7, + unicode_default_chset(), + NULL); + + if (s) + { + try { + name=s; + free(s); + } catch (...) { + free(s); + } + } +} + +mail::maildir::folder::~folder() +{ +} + +void mail::maildir::folder::sameServerAsHelperFunc() const +{ +} + +string mail::maildir::folder::getName() const +{ + if (path == "INBOX") + return hasSubFolders() ? "Folders":"INBOX"; + + return name; +} + +string mail::maildir::folder::getPath() const +{ + return path; +} + +bool mail::maildir::folder::hasMessages() const +{ + return hasMessagesFlag; +} + +bool mail::maildir::folder::hasSubFolders() const +{ + return hasSubfoldersFlag; +} + +bool mail::maildir::folder::isParentOf(string otherPath) const +{ + string s=path + "."; + + return (strncmp(otherPath.c_str(), s.c_str(), s.size()) == 0); +} + +void mail::maildir::folder::hasMessages(bool flag) +{ + hasMessagesFlag=flag; +} + +void mail::maildir::folder::hasSubFolders(bool flag) +{ + hasSubfoldersFlag=flag; +} + +class mail::maildir::indexSort { +public: + indexSort(); + ~indexSort(); + + bool operator()(const mail::maildir::maildirMessageInfo &a, + const mail::maildir::maildirMessageInfo &b); +}; + +mail::maildir::indexSort::indexSort() +{ +} + +mail::maildir::indexSort::~indexSort() +{ +} + +// Sort messages in some reasonable order. Rely on the timestamp component +// of the maildirfilename. + +bool mail::maildir::indexSort::operator() + (const mail::maildir::maildirMessageInfo &a, + const mail::maildir::maildirMessageInfo &b) +{ + unsigned long at=atol(a.lastKnownFilename.c_str()), + bt=atol(b.lastKnownFilename.c_str()); + + if ( at != bt) + return at < bt; + + return strcmp(a.lastKnownFilename.c_str(), + b.lastKnownFilename.c_str()) < 0; +} + +// Scan a maildirAccount + +bool mail::maildir::scan(string folderStr, vector<maildirMessageInfo> &index, + bool scanNew) +{ + string p; + + char *d=maildir_name2dir(path.c_str(), folderStr.c_str()); + + if (!d) + return false; + + try { + p=d; + free(d); + } catch (...) { + free(d); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + static const char * const subdirs[]={"/cur","/new"}; + + size_t i; + + for (i=0; i<(scanNew ? 2:1); i++) + { + string n=p + subdirs[i]; + + DIR *dirp=opendir(n.c_str()); + + try { + struct dirent *de; + + while (dirp && (de=readdir(dirp)) != NULL) + { + if (de->d_name[0] == '.') + continue; + + + maildirMessageInfo newInfo; + + newInfo.lastKnownFilename=de->d_name; + + // Use the filename as the uid + + newInfo.uid=de->d_name; + + size_t p=newInfo.uid.find(MDIRSEP[0]); + + if (p != std::string::npos) + newInfo.uid=newInfo.uid.substr(0, p); + + mail::maildir::updateFlags(de->d_name, + newInfo); + newInfo.recent= i > 0; + index.push_back(newInfo); + } + + if (dirp) + closedir(dirp); + } catch (...) { + if (dirp) + closedir(dirp); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + } + + sort(index.begin(), index.end(), indexSort()); + return true; +} + +void mail::maildir::folder::getParentFolder(callback::folderList &callback1, + callback &callback2) const +{ + if (isDestroyed(callback2)) + return; + + size_t n; + + n=path.rfind('.'); + + if (n == std::string::npos) + n=0; + + maildirAccount->findFolder(path.substr(0, n), + callback1, + callback2); +} + +void mail::maildir::folder::readFolderInfo( mail::callback::folderInfo + &callback1, + mail::callback &callback2) const +{ + if (isDestroyed(callback2)) + return; + + callback1.messageCount=0; + callback1.unreadCount=0; + + vector<maildirMessageInfo> dummyIndex; + + if (!maildirAccount->scan(path, dummyIndex, true)) + { + callback1.success(); + callback2.fail("Invalid folder"); + return; + } + + vector<maildirMessageInfo>::iterator b=dummyIndex.begin(), + e=dummyIndex.end(); + + while (b != e) + { + callback1.messageCount++; + if ( b->unread) + callback1.unreadCount++; + b++; + } + + callback1.success(); + callback2.success("OK"); +} + +mail::maildir::folder::listinfo::listinfo() +{ +} + +mail::maildir::folder::listinfo::~listinfo() +{ +} + +// Callback that lists maildirAccount folders. +// The callback filters only the folders under the list path + +void mail::maildir::folder::maildir_list_callback(const char *folder, + void *vp) +{ + mail::maildir::folder::listinfo *li= + (mail::maildir::folder::listinfo *)vp; + + if (strncmp(folder, li->path.c_str(), li->path.size()) || + folder[li->path.size()] != '.') + return; // Outside the hierarchy being listed. + + folder += li->path.size(); + ++folder; + + // If the remaining portion of the name has another period, there's + // a subdirectory there. Otherwise, it's a file. + // It's ok when we get multiple folders in the same subdirectory, + // subdirs is a STL set, which gets rid of duplicates + + const char *p=strchr(folder, '.'); + + if (p) + li->subdirs.insert(string(folder, p)); + else + li->list.insert(string(folder)); +} + +void mail::maildir::folder::readSubFolders( mail::callback::folderList + &callback1, + mail::callback &callback2) const +{ + if (isDestroyed(callback2)) + return; + + if (path.size() == 0) + { + maildirAccount->readTopLevelFolders(callback1, callback2); + return; + } + + listinfo li; + + li.path=path; + + maildir_list(maildirAccount->path.c_str(), + &mail::maildir::folder::maildir_list_callback, + &li); + + list<mail::folder *> folderList; + list<mail::folder *>::iterator b, e; + + try { + // Create a list of folder objects from the list of folder + // names in listinfo. Create a list in two passes. + + // First pass - build names of folders. If the folder is + // also found in the subdirectory list, make it a dual-purpose + // folder/directory. + + buildFolderList(folderList, &li.list, &li.subdirs); + + // Second pass - build remaining subdirs. + + buildFolderList(folderList, NULL, &li.subdirs); + + // Cleanup for the callback + vector<const mail::folder *> myList; + + b=folderList.begin(); + e=folderList.end(); + + while (b != e) + myList.push_back(*b++); + + callback1.success(myList); + callback2.success("OK"); + + } catch (...) { + b=folderList.begin(); + e=folderList.end(); + + while (b != e) + { + delete *b; + + b++; + } + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + b=folderList.begin(); + e=folderList.end(); + + while (b != e) + { + delete *b; + + b++; + } +} + +void mail::maildir::folder::buildFolderList(list<mail::folder *> &folderList, + set<string> *folders, + set<string> *dirs) const +{ + set<string>::iterator b, e; + + if (folders) + { + b=folders->begin(); + e=folders->end(); + } + else + { + b=dirs->begin(); + e=dirs->end(); + } + + while (b != e) + { + string name= *b++; + + folder *p=new folder(maildirAccount, path + "." + name); + + if (!p) + LIBMAIL_THROW(strerror(errno)); + + try { + if (folders) + { + p->hasMessages(true); + p->hasSubFolders(false); + + if (dirs->count(name) > 0) + // Also a subdir + { + p->hasSubFolders(true); + dirs->erase(name); + // Don't add this folder when we do + // a directory. + } + } + else + { + p->hasMessages(false); + p->hasSubFolders(true); + } + + folderList.push_back(p); + } catch (...) { + delete p; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + } +} + +mail::addMessage *mail::maildir::folder::addMessage(mail::callback + &callback) const +{ + if (isDestroyed(callback)) + return NULL; + + string folderPath; + + char *p=maildir_name2dir(maildirAccount->path.c_str(), path.c_str()); + + if (!p) + { + callback.fail(strerror(errno)); + return NULL; + } + + try { + folderPath=p; + free(p); + } catch (...) { + free(p); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + mail::maildir::addmessage *m=new + mail::maildir::addmessage(maildirAccount, folderPath, callback); + + if (!m) + { + callback.fail(strerror(errno)); + return NULL; + } + + return m; +} + +void mail::maildir::moveMessagesTo(const vector<size_t> &messages, + mail::folder *copyTo, + mail::callback &callback) +{ + sameServerFolderPtr=NULL; + copyTo->sameServerAsHelperFunc(); + + if (sameServerFolderPtr == NULL) + { + mail::account::moveMessagesTo(messages, copyTo, callback); + return; + } + + string destFolderPath; + + char *p=maildir_name2dir(path.c_str(), + sameServerFolderPtr->path.c_str()); + + if (!p) + { + callback.fail(strerror(errno)); + return; + } + + try { + destFolderPath=p; + free(p); + } catch (...) { + free(p); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + vector<size_t>::const_iterator b=messages.begin(), e=messages.end(); + + while (b != e) + { + size_t n=*b++; + + string messageFn=getfilename(n); + + if (messageFn.size() > 0) + { + string destName= destFolderPath + + messageFn.substr(messageFn.rfind('/')); + + rename(messageFn.c_str(), destName.c_str()); + } + } + updateFolderIndexInfo(&callback, false); +} + +void mail::maildir::folder::createSubFolder(string name, bool isDirectory, + mail::callback::folderList + &callback1, + mail::callback &callback2) const +{ + if (isDestroyed(callback2)) + return; + + // The name of the folder is translated from the local charset + // to modified UTF-7 (Courier-IMAP compatibility), with the following + // blacklisted characters: + + char *p=libmail_u_convert_tobuf(name.c_str(), unicode_default_chset(), + unicode_x_imap_modutf7 " ./~:", NULL); + + if (!p) + { + callback2.fail(strerror(errno)); + return; + } + + std::string nameutf7; + + errno=ENOMEM; + try { + nameutf7=p; + free(p); + } catch (...) { + free(p); + callback2.fail(strerror(errno)); + return; + } + + mail::maildir::folder newFolder(maildirAccount, path + "." + nameutf7); + + newFolder.hasMessagesFlag= ! (newFolder.hasSubfoldersFlag= + isDirectory); + + if (!newFolder.doCreate(isDirectory)) + { + callback2.fail(strerror(errno)); + return; + } + + vector<const mail::folder *> folders; + + folders.push_back(&newFolder); + callback1.success( folders ); + callback2.success("Mail folder created"); +} + +bool mail::maildir::folder::doCreate(bool isDirectory) const +{ + if (isDirectory) + return true; // Pretend + + if (maildirAccount->ispop3maildrop) + { + errno=EPERM; + return false; // POP3 maildrops don't have folders. + } + + string subdir; + + char *d=maildir_name2dir(maildirAccount->path.c_str(), path.c_str()); + // Checks for name validity. + + if (!d) + return false; + + try { + subdir=d; + free(d); + } catch (...) { + free(d); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + return mail::maildir::maildirmake(subdir, true); +} + + +bool mail::maildir::maildirmake(string subdir, bool isFolder) +{ + string nsubdir=subdir + "/new", + csubdir=subdir + "/cur", + tsubdir=subdir + "/tmp"; + + if (mkdir(subdir.c_str(), 0700) == 0) + { + if (mkdir(nsubdir.c_str(), 0700) == 0) + { + if (mkdir(tsubdir.c_str(), 0700) == 0) + { + if (mkdir(csubdir.c_str(), 0700) == 0) + { + if (!isFolder) + return true; + + string f=subdir + + "/maildirfolder"; + + int fd=::open(f.c_str(), + O_CREAT | + O_RDWR, 0666); + + if (fd >= 0) + { + close(fd); + return true; + } + rmdir(csubdir.c_str()); + } + rmdir(tsubdir.c_str()); + } + rmdir(nsubdir.c_str()); + } + rmdir(subdir.c_str()); + } + + return false; +} + +void mail::maildir::folder::create(bool isDirectory, + mail::callback &callback) const +{ + if (!doCreate(isDirectory)) + { + callback.fail(strerror(errno)); + } + else + { + callback.success("Mail folder created"); + } +} + +void mail::maildir::folder::destroy(mail::callback &callback, + bool destroyDir) const +{ + if (isDestroyed(callback)) + return; + + if (!destroyDir) // Folder directories are imaginary, cannot be nuked + { + string s; + char *d=maildir_name2dir(maildirAccount->path.c_str(), + path.c_str()); + if (!d) + { + callback.fail(strerror(errno)); + return; + } + + try { + s=d; + free(d); + } catch (...) { + free(d); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + if (!mail::maildir::maildirdestroy(s)) + { + callback.fail(strerror(errno)); + return; + } + } + + callback.success("Mail folder deleted"); +} + +void mail::maildir::folder::renameFolder(const mail::folder *newParent, + std::string newName, + mail::callback::folderList &callback1, + mail::callback &callback2) const +{ + if (isDestroyed(callback2)) + return; + + if (maildirAccount->folderPath.size() > 0) + { + size_t l=path.size(); + + if (strncmp(maildirAccount->folderPath.c_str(), + path.c_str(), l) == 0 && + ((maildirAccount->folderPath.c_str())[l] == 0 || + (maildirAccount->folderPath.c_str())[l] == '.')) + { + callback2.fail("Cannot RENAME currently open folder."); + return; + } + } + + // The name of the folder is translated from the local charset + // to modified UTF-7 (Courier-IMAP compatibility), with the following + // blacklisted characters: + + char *s=libmail_u_convert_tobuf(newName.c_str(), + unicode_default_chset(), + unicode_x_imap_modutf7 " ./~:", NULL); + + if (!s) + { + callback2.fail(strerror(errno)); + return; + } + + std::string nameutf7; + + errno=ENOMEM; + try { + nameutf7=s; + free(s); + } catch (...) { + free(s); + callback2.fail(strerror(errno)); + return; + } + + mail::maildir::folder newFolder(maildirAccount, + (newParent ? + newParent->getPath() + ".": + string("")) + nameutf7); + + newFolder.hasMessages( hasMessages() ); + newFolder.hasSubFolders( hasSubFolders() ); + + vector<const mail::folder *> folders; + + // Paths are INBOX.foo + + string from, to; + + char *p=maildir_name2dir(".", path.c_str()); + + if (p) + try { + from=p+2; // Skip ./ + free(p); + } catch (...) { + free(p); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + p=maildir_name2dir(".", newFolder.path.c_str()); + if (p) + try { + to=p+2; // Skip ./ + free(p); + } catch (...) { + free(p); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + + if (from.size() > 0 && + to.size() > 0 && + maildir_rename(maildirAccount->path.c_str(), + from.c_str(), to.c_str(), + MAILDIR_RENAME_FOLDER | + MAILDIR_RENAME_SUBFOLDERS, NULL)) + { + callback2.fail(strerror(errno)); + } + else + { + folders.push_back(&newFolder); + callback1.success( folders ); + callback2.success("Mail folder renamed"); + } +} + +bool mail::maildir::maildirdestroy(string d) +{ + list<string> contents; + + DIR *dirp=opendir(d.c_str()); + + try { + struct dirent *de; + + while (dirp && (de=readdir(dirp)) != NULL) + { + if (strcmp(de->d_name, ".") == 0) + continue; + if (strcmp(de->d_name, "..") == 0) + continue; + + contents.push_back(de->d_name); + } + + if (dirp) + closedir(dirp); + } catch (...) { + if (dirp) + closedir(dirp); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + list<string>::iterator b=contents.begin(), e=contents.end(); + + while (b != e) + { + string s=d + "/" + *b++; + + if (unlink(s.c_str()) < 0 && errno != ENOENT) + { + if (errno == EISDIR) + { + if (!maildirdestroy(s)) + return false; + continue; + } + return false; + } + } + rmdir(d.c_str()); + return true; +} + +mail::folder *mail::maildir::folder::clone() const +{ + if (isDestroyed()) + return NULL; + + mail::maildir::folder *p=new mail::maildir::folder(maildirAccount, + path); + + if (p) + { + p->hasMessagesFlag=hasMessagesFlag; + p->hasSubfoldersFlag=hasSubfoldersFlag; + return p; + } + return NULL; +} + + +void mail::maildir::findFolder(string folder, + mail::callback::folderList &callback1, + mail::callback &callback2) +{ + mail::maildir::folder tempFolder(this, folder); + + vector<const mail::folder *> folderList; + + folderList.push_back(&tempFolder); + + callback1.success(folderList); + callback2.success("OK"); +} + +string mail::maildir::translatePath(string path) +{ + return mail::mbox::translatePathCommon(path, ":/.~", "."); +} + +static string encword(string s) +{ + string r=""; + + string::iterator b=s.begin(), e=s.end(); + string::iterator p=b; + + while ( b != e ) + { + if ( *b == ':' || *b == '\\') + { + r.insert(r.end(), p, b); + r += "\\"; + p=b; + } + b++; + } + + r.insert(r.end(), p, b); + return r; +} + + +static string getword(string &s) +{ + string r=""; + + string::iterator b=s.begin(), e=s.end(), p=b; + + while (b != e) + { + if (*b == ':') + break; + + if (*b == '\\') + { + r.insert(r.end(), p, b); + + b++; + p=b; + + if (b == e) + break; + } + b++; + } + + r.insert(r.end(), p, b); + + if (b != e) + { + b++; + s=string(b, s.end()); + } + + return r; +} + +string mail::maildir::folder::toString() const +{ + return encword(path) + ":" + encword(name) + ":" + + (hasMessagesFlag ? "M":"") + + (hasSubfoldersFlag ? "S":""); +} + + +mail::folder *mail::maildir::folderFromString(string folderName) +{ + string path=getword(folderName); + string name=getword(folderName); + + mail::maildir::folder *f=new mail::maildir::folder(this, path); + + if (!f) + return NULL; + + f->hasMessagesFlag= folderName.find('M') != std::string::npos; + f->hasSubfoldersFlag= folderName.find('S') != std::string::npos; + + return f; +} + +void mail::maildir::folder::open(mail::callback &openCallback, + mail::snapshot *restoreSnapshot, + mail::callback::folder &folderCallback) const +{ + if (isDestroyed(openCallback)) + return; + + maildirAccount->open(path, openCallback, folderCallback); +} + + +void mail::maildir::readTopLevelFolders(mail::callback::folderList &callback1, + mail::callback &callback2) +{ + mail::maildir::folder inbox(this, INBOX); + mail::maildir::folder folders(this, INBOX); + + inbox.hasSubfoldersFlag=false; + folders.hasMessagesFlag=false; + + vector<const mail::folder *> folderList; + + folderList.push_back(&inbox); + + if (!ispop3maildrop) + folderList.push_back(&folders); + + callback1.success(folderList); + callback2.success("OK"); +} diff --git a/libmail/maildirfolder.H b/libmail/maildirfolder.H new file mode 100644 index 0000000..e7710b0 --- /dev/null +++ b/libmail/maildirfolder.H @@ -0,0 +1,108 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_maildirfolder_H +#define libmail_maildirfolder_H + +#include "maildir.H" + +#include <string> +#include <set> +#include <list> + +LIBMAIL_START + +//////////////////////////////////////////////////////////////////////////// +// +// Courier-compatible maildir folder. + +class maildir::folder : public mail::folder { + + maildir *maildirAccount; + + std::string path; + std::string name; + + bool hasMessagesFlag; + bool hasSubfoldersFlag; + + // Compile subdirectory listings + + class listinfo { + public: + listinfo(); + ~listinfo(); + + std::string path; + + std::set<std::string> list, subdirs; + }; + + static void maildir_list_callback(const char *, void *); + +public: + friend class maildir; + + folder(maildir *maildirArg, + std::string pathArg); + ~folder(); + + void sameServerAsHelperFunc() const; + + std::string getName() const; + std::string getPath() const; + + bool hasMessages() const; + bool hasSubFolders() const; + bool isParentOf(std::string path) const; + + void hasMessages(bool); + void hasSubFolders(bool); + + void getParentFolder(callback::folderList &callback1, + callback &callback2) const; + + void readFolderInfo( mail::callback::folderInfo &callback1, + mail::callback &callback2) const; + + void readSubFolders( mail::callback::folderList &callback1, + mail::callback &callback2) const; + +private: + void buildFolderList(std::list<mail::folder *> &folderList, + std::set<std::string> *folders, + std::set<std::string> *dirs) const; + + bool doCreate(bool isDirectory) const; + +public: + mail::addMessage *addMessage(mail::callback &callback) const; + + void createSubFolder(std::string name, bool isDirectory, + mail::callback::folderList &callback1, + mail::callback &callback2) const; + + void create(bool isDirectory, + mail::callback &callback) const; + + void destroy(mail::callback &callback, bool destroyDir) const; + + void renameFolder(const mail::folder *newParent, std::string newName, + mail::callback::folderList &callback1, + mail::callback &callback) const; + + mail::folder *clone() const; + std::string toString() const; + + void open(mail::callback &openCallback, + snapshot *restoreSnapshot, + mail::callback::folder &folderCallback) const; +}; + +LIBMAIL_END + +#endif + + diff --git a/libmail/mailtool.C b/libmail/mailtool.C new file mode 100644 index 0000000..b060507 --- /dev/null +++ b/libmail/mailtool.C @@ -0,0 +1,1340 @@ +/* +** Copyright 2002-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "mail.H" +#include "misc.H" +#include "logininfo.H" +#include "sync.H" +#include "envelope.H" +#include "structure.H" +#include "maildir.H" +#include "rfcaddr.H" +#include "addmessage.H" +#include "smtpinfo.H" +#include "rfc822/rfc822.h" +#include <cstring> + +#if HAVE_TERMIOS_H +#include <termios.h> +#endif + +#include <iostream> +#include <iomanip> +#include <algorithm> +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> + +#include <sstream> +#include <errno.h> + +using namespace std; + +static void error(string errmsg) +{ + cerr << "ERROR: " << errmsg << endl; + exit (1); +} + +static void error(mail::ACCOUNT *p) +{ + error(p->errmsg); +} + +extern "C" void rfc2045_error(const char *p) +{ + cerr << "ERROR: " << p << endl; + exit (0); +} + +static void showenvelope(const mail::envelope &env, string pfix="") +{ + if (env.date > 0) + { + char buffer[200]; + + rfc822_mkdate_buf(env.date, buffer); + cout << pfix << " Date: " << buffer + << endl; + } + + cout << pfix << " Subject: " << env.subject << endl; + cout << pfix << "In-ReplyTo: " << env.inreplyto + << endl; + + vector<string>::const_iterator b=env.references.begin(), + e=env.references.end(); + + while (b != e) + { + cout << pfix << " Reference: <" << *b << ">" << endl; + b++; + } + + cout << pfix << "Message-ID: " << env.messageid + << endl; + cout << pfix << mail::address::toString(" From: ", + env.from) + << endl; + cout << pfix << mail::address::toString(" Sender: ", + env.sender) + << endl; + cout << pfix << mail::address::toString(" Reply-To: ", + env.replyto) + << endl; + cout << pfix << mail::address::toString(" To: ", + env.to) + << endl; + cout << pfix << mail::address::toString(" Cc: ", + env.cc) + << endl; + cout << pfix << mail::address::toString(" Bcc: ", + env.bcc) + << endl; +} + +static void showstructure(const mail::mimestruct &mps, string pfix="") +{ + cout << pfix << " Mime-ID: " << mps.mime_id << endl; + cout << pfix << " Content-Type: " << mps.type << "/" + << mps.subtype << endl; + + mail::mimestruct::parameterList::const_iterator b, e; + + b=mps.type_parameters.begin(); + e=mps.type_parameters.end(); + + while (b != e) + { + cout << pfix << " " + << b->first << "=" << b->second << endl; + b++; + } + + cout << pfix << " Content-Id: " << mps.content_id << endl; + cout << pfix << " Content-Description: " << mps.content_description + << endl; + cout << pfix << "Content-Transfer-Encoding: " + << mps.content_transfer_encoding << endl; + cout << pfix << " Size: " << mps.content_size + << endl; + cout << pfix << " Lines: " << mps.content_lines + << endl; + cout << pfix << " Content-MD5: " << mps.content_md5 + << endl; + cout << pfix << " Content-Language: " << mps.content_language + << endl; + cout << pfix << " Content-Disposition: " + << mps.content_disposition << endl; + + b=mps.content_disposition_parameters.begin(); + e=mps.content_disposition_parameters.end(); + + while (b != e) + { + cout << pfix << " " + << b->first << "=" << b->second << endl; + b++; + } + + if (mps.messagerfc822()) + { + showenvelope(mps.getEnvelope(), pfix + " "); + } + + size_t n=mps.getNumChildren(); + size_t i; + + for (i=0; i<n; i++) + { + cout << pfix + << "---------------------------------------------------" + << endl; + showstructure(*mps.getChild(i), pfix + " "); + } +} + +static void showFolderList(mail::ACCOUNT *p, + mail::ACCOUNT::FolderList &list, int nestinglevel, + bool tree) +{ + vector<const mail::folder *> cpy; + + cpy.insert(cpy.end(), list.begin(), list.end()); + + sort(cpy.begin(), cpy.end(), mail::folder::sort(false)); + + vector<const mail::folder *>::iterator + b=cpy.begin(), e=cpy.end(); + + bool extraSpace=false; + + while (b != e) + { + const mail::folder *f= *b++; + + if (extraSpace) + cout << endl; + + extraSpace=false; + + if (tree) + { + mail::ACCOUNT::FolderInfo info; + + if (f->hasMessages()) + p->readFolderInfo(f, info); + + cout << setw(nestinglevel * 4 + 1) << " " << setw(0) + << f->getName(); + + if (info.unreadCount || info.messageCount) + { + cout << " ("; + if (info.messageCount) + { + cout << info.messageCount << " messages"; + if (info.unreadCount) + cout << ", "; + } + + if (info.unreadCount) + cout << info.unreadCount << " unread"; + cout << ")"; + } + + cout << endl; + } + else + { + cout << f->getPath() << endl; + } + + if (f->hasSubFolders() || !f->hasMessages()) + { + mail::ACCOUNT::FolderList subfolders; + + if (p->getSubFolders(f, subfolders)) + { + showFolderList(p, subfolders, nestinglevel+1, + tree); + extraSpace=tree; + } + else if (f->hasSubFolders()) + { + error(p); + } + } + } +} + +static void showEnvelope(mail::xenvelope &env) +{ + char date[100]; + + if (env.arrivalDate) + { + rfc822_mkdate_buf(env.arrivalDate, date); + + cout << " Arrival-Date: " << date << endl; + } + + cout << " Size: " << env.messageSize << endl; + + if (env.date) + { + rfc822_mkdate_buf(env.date, date); + + cout << " Date: " << date << endl; + } + + cout << " Subject: " << env.subject << endl; + cout << " Message-Id: " << env.messageid << endl; + if (env.inreplyto.size() > 0) + cout << " In-Reply-To: " << env.inreplyto << endl; + + vector<string>::const_iterator b=env.references.begin(), + e=env.references.end(); + + while (b != e) + { + cout << " Reference: <" << *b << ">" << endl; + b++; + } + + if (env.from.size() > 0) + cout << mail::address::toString(" From: ", env.from) + << endl; + + if (env.sender.size() > 0) + cout << mail::address::toString(" Sender: ", env.sender) + << endl; + + if (env.replyto.size() > 0) + cout << mail::address::toString(" Reply-To: ", + env.replyto)<< endl; + if (env.to.size() > 0) + cout << mail::address::toString(" To: ", env.to) + << endl; + + if (env.cc.size() > 0) + cout << mail::address::toString(" Cc: ", + env.cc) << endl; + + if (env.bcc.size() > 0) + cout << mail::address::toString(" Bcc: ", env.bcc) + << endl; +} + +static bool getIndex(mail::ACCOUNT *p, mail::folder *f) +{ + if (!p->openFolder(f, NULL)) + return false; + + size_t n=p->getFolderIndexSize(); + size_t i; + + vector<size_t> msgnums; + + cout << "Total: " << n << " messages." << endl; + +#if 1 + for (i=0; i<n; i++) + msgnums.push_back(i); + + vector<mail::xenvelope> envelopes; + + if (!p->getMessageEnvelope(msgnums, envelopes)) + return false; + + for (i=0; i<n; i++) + { + if (i > 0) + cout << endl; + cout << "Message " << (i+1) << ":" << endl; + + showEnvelope(envelopes[i]); + } +#endif + +#if 0 + vector<mail::mimestruct> structures; + + if (!p->getMessageStructure(msgnums, structures)) + error(p); + + vector<mail::mimestruct>::iterator b=structures.begin(), + e=structures.end(); + + i=0; + while (b != e) + { + cout << endl; + cout << "-------------------------------------------" << endl; + cout << " Message " << ++i << endl; + cout << "-------------------------------------------" << endl; + showstructure( *b++ ); + } +#endif + return true; + +} + +class DisplayHeader : public mail::ACCOUNT::Store { +public: + DisplayHeader(); + ~DisplayHeader(); + void store(size_t, string); +}; + +DisplayHeader::DisplayHeader() +{ +} + +DisplayHeader::~DisplayHeader() +{ +} + +void DisplayHeader::store(size_t dummy, string txt) +{ + cout << txt; +} + +static bool getHeaders(mail::ACCOUNT *p, mail::folder *f, size_t n) +{ + if (!p->openFolder(f, NULL)) + return false; + + vector<size_t> v; + + v.push_back(n-1); + + DisplayHeader display_header; + + return (p->getMessageContent(v, false, mail::readHeadersFolded, + display_header)); +} + +static bool removeMessages(mail::ACCOUNT *p, mail::folder *f, const char *msgs) +{ + if (!p->openFolder(f, NULL)) + return false; + + vector<size_t> v; + + while (*msgs) + { + if (!isdigit(*msgs)) + { + msgs++; + continue; + } + + size_t n=0; + + while (*msgs && isdigit(*msgs)) + n= n * 10 + (*msgs++ - '0'); + + if (n > 0) + { + cout << "Removing " << n << endl; + v.push_back(n-1); + } + } + + cout << "Ready: "; + + string reply; + + if (getline(cin, reply).fail()) + return true; + + return (p->removeMessages(v)); +} + +static bool doFilterFolder(mail::ACCOUNT *p, mail::folder *f) +{ + if (!p->openFolder(f, NULL)) + return false; + + size_t n=p->getFolderIndexSize(); + + cout << "Total: " << n << " messages." << endl; + + size_t i; + + for (i=0; i<n; i++) + { + vector<size_t> msgnums; + + msgnums.push_back(i); + + vector<mail::xenvelope> envelopes; + + if (!p->getMessageEnvelope(msgnums, envelopes)) + return false; + + cout << endl << "Message " << (i+1) << ":" << endl; + showEnvelope(envelopes[0]); + + cout << "D)elete, S)kip, E)xit? (S) " << flush; + + string reply; + + if (getline(cin, reply).fail()) + return true; + + if (reply.size() == 0) + continue; + + mail::messageInfo msgInfo=p->getFolderIndexInfo(n); + + switch (reply[0]) { + case 'D': + case 'd': + msgInfo.deleted=true; + + if (!p->saveFolderIndexInfo(n, msgInfo)) + return false; + continue; + case 'E': + case 'e': + break; + default: + continue; + } + break; + } + + p->updateFolderIndexInfo(); + + return true; +} + +static bool doUploadMessage(mail::ACCOUNT *p, mail::folder *f) +{ + class uploadMessage : public mail::addMessagePull { + public: + + string getMessageContents() + { + char buffer[BUFSIZ]; + + int n=read(0, buffer, sizeof(buffer)); + + if (n <= 0) + return ""; + + return (string(buffer, buffer+n)); + } + }; + + uploadMessage upload; + + return p->addMessage(f, upload); +} + +class readStdin : public mail::addMessagePull { +public: + readStdin(); + ~readStdin(); + string getMessageContents(); +}; + +readStdin::readStdin() +{ +} + +readStdin::~readStdin() +{ +} + +string readStdin::getMessageContents() +{ + char buffer[BUFSIZ]; + + int n=cin.read(buffer, sizeof(buffer)).gcount(); + + if (n <= 0) + return ""; + + return string(buffer, buffer+n); +} + +class copyProgressReporter : public mail::ACCOUNT::Progress { + + string lastmsg; + + static void fmtByte(ostringstream &o, size_t byteCount); + + size_t lastCompletedShown; + +public: + size_t bytesCompleted, bytesEstimatedTotal, + messagesCompleted, messagesEstimatedTotal; + + bool doReportProgress; + time_t timestamp; + bool final; + + copyProgressReporter(mail::ACCOUNT *acct); + ~copyProgressReporter(); + + void operator()(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + + void operator()(); +}; + +copyProgressReporter::copyProgressReporter(mail::ACCOUNT *acct) + : Progress(acct), lastmsg(""), + lastCompletedShown(0), + bytesCompleted(0), bytesEstimatedTotal(0), + messagesCompleted(0), messagesEstimatedTotal(0), + doReportProgress(isatty(1) != 0), timestamp(time(NULL)), + final(false) +{ +} + +copyProgressReporter::~copyProgressReporter() +{ +} + +void copyProgressReporter::operator()(size_t bytesCompletedArg, + size_t bytesEstimatedTotalArg, + + size_t messagesCompletedArg, + size_t messagesEstimatedTotalArg) +{ + bytesCompleted=bytesCompletedArg; + bytesEstimatedTotal=bytesEstimatedTotalArg; + messagesCompleted=messagesCompletedArg; + messagesEstimatedTotal=messagesEstimatedTotalArg; + + if (!doReportProgress) + return; + + time_t t=time(NULL); + + if (t == timestamp) + return; + + timestamp=t; + + (*this)(); +} + +void copyProgressReporter::operator()() +{ + if (lastCompletedShown != messagesCompleted) + bytesCompleted=bytesEstimatedTotal=0; + // Background noise. + + lastCompletedShown=messagesCompleted; + + size_t i, n=lastmsg.size(); + + for (i=0; i<n; i++) + cout << '\b'; + for (i=0; i<n; i++) + cout << ' '; + for (i=0; i<n; i++) + cout << '\b'; + + ostringstream o; + + if (bytesCompleted) + { + fmtByte(o, bytesCompleted); + + if (bytesCompleted < bytesEstimatedTotal) + { + o << " of "; + fmtByte(o, bytesEstimatedTotal); + } + } + + if (final || messagesEstimatedTotal > 1) + { + if (bytesCompleted) + o << "; "; + + o << messagesCompleted; + if (messagesCompleted < messagesEstimatedTotal) + o << " of " << messagesEstimatedTotal; + o << " msgs copied..."; + } + + lastmsg=o.str(); + cout << lastmsg << flush; +} + +void copyProgressReporter::fmtByte(ostringstream &o, size_t byteCount) +{ + if (byteCount < 1024) + { + o << byteCount; + return; + } + + if (byteCount < 1024 * 1024) + { + o << (byteCount + 512) / 1024 << " Kb"; + return; + } + + o << byteCount / (1024 * 1024) << "." + << (byteCount % (1024 * 1024)) * 10 / (1024 * 1024) << " Mb"; +} + +static string docopyfolderordir(mail::ACCOUNT *fromAccount, + mail::ACCOUNT *toAccount, + mail::folder *fromfolder, + mail::folder *tofolder); + +static string docopyfolder(mail::ACCOUNT *fromAccount, + mail::ACCOUNT *toAccount, + mail::folder *fromfolder, + mail::folder *tofolder); + + +static string docopy(mail::ACCOUNT *fromAccount, + mail::ACCOUNT *toAccount, + string fromFolderName, + string toFolderName, + bool recurse) +{ + string fromNameServer=fromFolderName, toNameServer=toFolderName; + + mail::folder *fromfolder= + fromAccount->getFolderFromPath(fromNameServer); + + if (!fromfolder) + return fromAccount->errmsg; + + mail::folder *tofolder= + toAccount->getFolderFromPath(toNameServer); + + if (!tofolder) + return toAccount->errmsg; + + string errmsg=recurse ? + docopyfolderordir(fromAccount, toAccount, + fromfolder, tofolder) + : docopyfolder(fromAccount, toAccount, + fromfolder, tofolder); + + delete fromfolder; + delete tofolder; + return errmsg; +} + +static string docopyfolderordir(mail::ACCOUNT *fromAccount, + mail::ACCOUNT *toAccount, + mail::folder *fromfolder, + mail::folder *tofolder) +{ + if (fromfolder->hasSubFolders()) + { + toAccount->createFolder(tofolder, true); + // Ignore error, subdirectory may already exist + + mail::ACCOUNT::FolderList subfolders; + + if (!fromAccount->getSubFolders(fromfolder, subfolders)) + return fromfolder->getName() + ": " + + fromAccount->errmsg; + + mail::ACCOUNT::FolderList::iterator b=subfolders.begin(), + e=subfolders.end(); + + while (b != e) + { + mail::folder *f= *b++; + string n=f->getName(); + + mail::folder *newFolder= + toAccount->createFolder(tofolder, + n, + !f->hasMessages()); + + if (!newFolder) // May already exist + { + mail::ACCOUNT::FolderList subfolders2; + + if (!toAccount->getSubFolders(tofolder, + subfolders2)) + return tofolder->getName() + ": " + + toAccount->errmsg; + + mail::ACCOUNT::FolderList::iterator + sb=subfolders2.begin(), + se=subfolders2.end(); + + while (sb != se) + { + if ( (*sb)->getName() == n) + { + newFolder= (*sb)->clone(); + + if (!newFolder) + return strerror(errno); + break; + } + sb++; + } + + if (!newFolder) + { + return tofolder->getName() + + ": cannot create " + + n; + } + } + + string errmsg=docopyfolderordir(fromAccount, + toAccount, + f, + newFolder); + + if (errmsg != "") + return errmsg; + delete newFolder; + } + if (!fromfolder->hasMessages()) + return ""; + } + + return docopyfolder(fromAccount, toAccount, fromfolder, tofolder); +} + +static string docopyfolder(mail::ACCOUNT *fromAccount, + mail::ACCOUNT *toAccount, + mail::folder *fromfolder, + mail::folder *tofolder) +{ + if (!fromAccount->openFolder(fromfolder, NULL)) + return fromfolder->getName() + ": " + + fromAccount->errmsg; + + toAccount->createFolder(tofolder, false); // May fail, ignore + + { + mail::ACCOUNT::FolderInfo dummyInfo; + + if (!toAccount->readFolderInfo(tofolder, dummyInfo)) + return tofolder->getName() + ": " + toAccount->errmsg; + } + + size_t n=fromAccount->getFolderIndexSize(); + + vector<size_t> copyvec; + + copyvec.reserve(n); + size_t i; + + for (i=0; i<n; i++) + copyvec.push_back(i); + + { + copyProgressReporter progressReport(fromAccount); + + cout << fromfolder->getName() << ": "; + + if (!fromAccount->copyMessagesTo(copyvec, tofolder)) + { + progressReport(); + cout << endl; + return fromAccount->errmsg; + } + progressReport.bytesCompleted=0; + progressReport.bytesEstimatedTotal=0; + progressReport.messagesCompleted=n; + progressReport.messagesEstimatedTotal=n; + progressReport.final=true; + progressReport(); + cout << endl; + } + return ""; +} + +static std::string pw_url; + +class pw_prompt : public mail::loginCallback { +public: + pw_prompt(); + ~pw_prompt(); + + void loginPrompt(callbackType cbType, + std::string prompt); +}; + +pw_prompt::pw_prompt() +{ +} + +pw_prompt::~pw_prompt() +{ +} + + +void pw_prompt::loginPrompt(mail::loginCallback::callbackType theType, + std::string prompt) +{ +#if HAVE_TERMIOS_H + struct termios ti; +#endif + + if (isatty(1)) + { + if (pw_url.size() > 0) + cout << pw_url << "> "; + + cout << prompt; + +#if HAVE_TERMIOS_H + if (theType == PASSWORD) + { + struct termios ti2; + + if (tcgetattr(0, &ti)) + { + perror("tcgetattr"); + } + + ti2=ti; + + ti2.c_lflag &= ~ECHO; + tcsetattr(0, TCSAFLUSH, &ti2); + } +#endif + } + + char linebuf[80]; + + cin.getline(linebuf, sizeof(linebuf)); + +#if HAVE_TERMIOS_H + if (isatty(1)) + { + if (theType == PASSWORD) + { + cout << endl; + tcsetattr(0, TCSAFLUSH, &ti); + } + } +#endif + + char *p=strchr(linebuf, '\n'); + + if (p) *p=0; + + callback(string(linebuf)); +} + +static void getExtraString(mail::account::openInfo &loginInfoArg) +{ + if (strncmp(loginInfoArg.url.c_str(), "nntp", 4) == 0) + { + const char *p=getenv("NEWSRC"); + + if (p && *p) + loginInfoArg.extraString=p; + else + loginInfoArg.extraString= + mail::homedir() + "/.newsrc"; + return; + } + + if (strncmp(loginInfoArg.url.c_str(), "pop3maildrop", 12) == 0) + { + cout << "maildrop (./Maildir): " << flush; + + char linebuf[80]; + + cin.getline(linebuf, sizeof(linebuf)); + + char *p=strchr(linebuf, '\n'); + + if (p) *p=0; + + if (linebuf[0] == 0) + strcpy(linebuf, "Maildir"); + + loginInfoArg.extraString=linebuf; + if (*loginInfoArg.extraString.c_str() != '/') + { + if (getcwd(linebuf, sizeof(linebuf)) == NULL) + { + perror("getcwd"); + exit(1); + } + + loginInfoArg.extraString= string(linebuf) + "/" + + loginInfoArg.extraString; + } + mail::maildir::maildirmake(loginInfoArg.extraString, false); + } +} + +int main(int argc, char **argv) +{ + int argn=1; + const char *messagenum="0"; + + string path, newpath, name; + bool doCreate=false; + bool doCreateDir=false; + bool doDelete=false; + bool doDeleteDir=false; + bool doTree=false; + bool doList=false; + bool doIndex=false; + bool doHeaders=false; + bool doRemove=false; + bool doFilter=false; + bool doUpload=false; + bool doMail=false; + bool doRename=false; + + bool doCopy=false; + bool doRecurse=false; + string copyto; + string tofolder; + string fromfolder; + + mail::smtpInfo smtpInfo; + + while (argn < argc) + { + if (strcmp(argv[argn], "-mailfrom") == 0 && argc - argn >= 2) + { + doMail=true; + smtpInfo.sender=argv[++argn]; + ++argn; + continue; + } + + if (strcmp(argv[argn], "-to") == 0 && argc - argn >= 2) + { + smtpInfo.recipients.push_back(argv[++argn]); + ++argn; + continue; + } + + if (strcmp(argv[argn], "-opt") == 0 && argc - argn >= 3) + { + string opt=argv[++argn]; + string val=argv[++argn]; + + smtpInfo.options.insert(make_pair(opt, val)); + ++argn; + continue; + } + + if (strcmp(argv[argn], "-create") == 0 && argc - argn >= 3) + { + path=argv[++argn]; + name=argv[++argn]; + ++argn; + doCreate=true; + continue; + } + + if (strcmp(argv[argn], "-rename") == 0 && argc - argn >= 4) + { + path=argv[++argn]; + newpath=argv[++argn]; + name=argv[++argn]; + ++argn; + doRename=true; + continue; + } + + if (strcmp(argv[argn], "-createdir") == 0 && argc - argn >= 3) + { + path=argv[++argn]; + name=argv[++argn]; + ++argn; + doCreateDir=true; + continue; + } + + if (strcmp(argv[argn], "-delete") == 0 && argc - argn >= 2) + { + path=argv[++argn]; + ++argn; + doDelete=true; + continue; + } + + if (strcmp(argv[argn], "-deletedir") == 0 && argc - argn >= 2) + { + path=argv[++argn]; + ++argn; + doDeleteDir=true; + continue; + } + + if (strcmp(argv[argn], "-tree") == 0) + { + doTree=true; + ++argn; + continue; + } + + if (strcmp(argv[argn], "-list") == 0) + { + doList=true; + ++argn; + continue; + } + + if (strcmp(argv[argn], "-index") == 0 && argc - argn >= 2) + { + path=argv[++argn]; + ++argn; + doIndex=true; + continue; + } + + if (strcmp(argv[argn], "-upload") == 0 && argc - argn >= 2) + { + path=argv[++argn]; + ++argn; + doUpload=true; + continue; + } + + if (strcmp(argv[argn], "-filter") == 0 && argc - argn >= 2) + { + path=argv[++argn]; + ++argn; + doFilter=true; + continue; + } + + if (strcmp(argv[argn], "-headers") == 0 && argc - argn >= 3) + { + path=argv[++argn]; + messagenum=argv[++argn]; + ++argn; + doHeaders=true; + continue; + } + + if (strcmp(argv[argn], "-remove") == 0 && argc - argn >= 3) + { + path=argv[++argn]; + messagenum=argv[++argn]; + ++argn; + doRemove=true; + continue; + } + + if (strcmp(argv[argn], "-copyto") == 0 && argc - argn >= 2) + { + copyto=argv[++argn]; + ++argn; + doCopy=true; + continue; + } + + if (strcmp(argv[argn], "-tofolder") == 0 && argc - argn >= 2) + { + tofolder=argv[++argn]; + ++argn; + continue; + } + + if (strcmp(argv[argn], "-fromfolder") == 0 && argc - argn >= 2) + { + fromfolder=argv[++argn]; + ++argn; + continue; + } + + if (strcmp(argv[argn], "-recurse") == 0) + { + doRecurse=true; + ++argn; + continue; + } + break; + } + + if (argn >= argc) + exit (0); + + string url=argv[argn]; + + mail::ACCOUNT *p=new mail::ACCOUNT(); + + if (!p) + { + cerr << strerror(errno) << endl; + exit (0); + } + + mail::ACCOUNT::FolderList folderList; + + pw_url=url; + + { + pw_prompt getpassword; + + mail::account::openInfo loginInfoArg; + + loginInfoArg.url=url; + + getExtraString(loginInfoArg); + + loginInfoArg.loginCallbackObj= &getpassword; + if (!p->login(loginInfoArg) || + !p->getTopLevelFolders(folderList)) + { + error(p); + } + } + + if (doCopy) + { + mail::ACCOUNT *toAccount=new mail::ACCOUNT(); + + if (!toAccount) + { + cerr << strerror(errno) << endl; + exit (0); + } + + pw_url=copyto; + + pw_prompt getpassword; + + mail::account::openInfo loginInfo; + + loginInfo.url=copyto; + loginInfo.loginCallbackObj= &getpassword; + getExtraString(loginInfo); + + if (!toAccount->login(loginInfo)) + { + error(toAccount); + } + + string errmsg=docopy(p, toAccount, fromfolder, tofolder, + doRecurse); + + if (errmsg.size() > 0) + error(errmsg); + toAccount->logout(); + delete toAccount; + } + else if (doCreate || doCreateDir) + { + mail::folder *f=p->getFolderFromPath(path); + + if (!f || !(f=p->createFolder(f, name, doCreateDir))) + { + error(p); + } + } + else if (doRename) + { + mail::folder *oldFolder=p->getFolderFromPath(path); + + if (!oldFolder) + { + cerr << "ERROR: " << path << " - folder not found." + << endl; + exit(0); + } + + mail::folder *newParent=NULL; + + if (newpath.size() > 0) + { + newParent=p->getFolderFromPath(newpath); + + if (!newParent) + { + cerr << "ERROR: " << newpath + << " - folder not found." + << endl; + exit(0); + } + } + + if (!(oldFolder=p->renameFolder(oldFolder, newParent, name))) + error(p); + } + else if (doDelete || doDeleteDir) + { + mail::folder *f=p->getFolderFromPath(path); + + if (!f || !p->deleteFolder(f, doDeleteDir)) + { + error(p); + } + } + else if (doIndex) + { + mail::folder *f=p->getFolderFromPath(path); + + if (!f || !getIndex(p, f)) + error(p); + +#if 0 + cout << "TOTAL # OF MESSAGES: " << p->getFolderIndexSize() + << endl; + + string dummy; + + getline(cin, dummy); + + cout << "CHECK NEW MAIL: " << p->checkNewMail() << endl; + + cout << "TOTAL # OF MESSAGES: " << p->getFolderIndexSize() + << endl; + + if (!getIndex(p, f)) + error(p); +#endif + } + else if (doHeaders) + { + mail::folder *f=p->getFolderFromPath(path); + + if (!f || !getHeaders(p, f, atoi(messagenum))) + error(p); + } + else if (doRemove) + { + mail::folder *f=p->getFolderFromPath(path); + + if (!f || !removeMessages(p, f, messagenum)) + error(p); + } + else if (doFilter) + { + mail::folder *f=p->getFolderFromPath(path); + + if (!f || !doFilterFolder(p, f)) + error(p); + } + else if (doUpload) + { + mail::folder *f=p->getFolderFromPath(path); + + if (!f || !doUploadMessage(p, f)) + error(p); + } + else if (doMail) + { + readStdin doReadStdin; + + if (!p->send(smtpInfo, NULL, doReadStdin)) + { + string errmsg=p->errmsg; + p->logout(); + delete p; + error(errmsg); + } + } + else if (doTree || doList) + { + mail::ACCOUNT::FolderList::iterator b=folderList.begin(), + e=folderList.end(); + + while (b != e) + { + if ( (*b)->getPath().size() == 0) + break; + + ++b; + } + + if (b != e) + { + mail::ACCOUNT::FolderList subfolders; + + if (!p->getSubFolders(*b, subfolders)) + error(p); + + showFolderList(p, subfolders, 0, doTree); + } + else + showFolderList(p, folderList, 0, doTree); + } + + p->logout(); + + delete p; + exit(0); +} 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, "./~:", "/"); +} diff --git a/libmail/mbox.H b/libmail/mbox.H new file mode 100644 index 0000000..f736503 --- /dev/null +++ b/libmail/mbox.H @@ -0,0 +1,470 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_mbox_H +#define libmail_mbox_H + +#include "libmail_config.h" +#include "mail.H" + +#include "unicode/unicode.h" +#include "maildir/maildirkeywords.h" + +#include <time.h> +#include <unistd.h> +#include <stdio.h> +#include <queue> +#include <map> +#include <set> +#include <vector> + +#include "mboxmagictag.H" +#include "generic.H" + +#include "namespace.H" + +struct ll_mail; + +LIBMAIL_START + +class file; + +///////////////////////////////////////////////////////////////////////// +// +// Legacy mbox driver. +// +// +class mbox : public mail::account, public generic { + + void resumed(); + void handler(std::vector<pollfd> &fds, int &timeout); + + bool calledDisconnected; // True if the disconnect callback was invoked + bool magicInbox; // True if this account uses INBOX + std::string inboxSpoolPath; // Path to the system spool inbox + std::string inboxMailboxPath; // $HOME/mboxAccount + + std::string rootPath; // Root path for folders. + + class folder : public mail::folder { + + std::string path; + std::string name; + mbox &mboxAccount; + + bool saveHasMessages; + bool saveHasFolders; + + class closeCallback : public mail::callback { + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) {} + //TODO + + public: + ptr<mbox> origmbox; + mail::callback &origCallback; + mail::callback::folder &origFolderCallback; + std::string origPath; + + closeCallback(mbox *origmboxArg, + mail::callback &origCallbackArg, + mail::callback::folder + &origFolderCallbackarg, + std::string origPathArg); + ~closeCallback(); + + void success(std::string); + void fail(std::string); + }; + + class add; + + public: + friend class mbox; + + folder(std::string pathArg, + mbox &mboxArg); + ~folder(); + + folder(const mbox::folder &); + + static std::string defaultName(std::string path); + + void sameServerAsHelperFunc() const; + + std::string getName() const; + std::string getPath() const; + + bool hasMessages() const; + bool hasSubFolders() const; + + bool isParentOf(std::string path) const; + + void hasMessages(bool); + void hasSubFolders(bool); + + + void getParentFolder(callback::folderList &callback1, + callback &callback2) const; + + void readFolderInfo( mail::callback::folderInfo + &callback1, + mail::callback &callback2) const; + + void readSubFolders( mail::callback::folderList &callback1, + mail::callback &callback2) const; + + mail::addMessage *addMessage(mail::callback &callback) const; + + void createSubFolder(std::string name, bool isDirectory, + mail::callback::folderList &callback1, + mail::callback &callback2) const; + + void create(bool isDirectory, + mail::callback &callback) const; + + void destroy(mail::callback &callback, bool destroyDir) + const; + + void renameFolder(const mail::folder *newParent, + std::string newName, + mail::callback::folderList &callback1, + callback &callback) const; + + mail::folder *clone() const; + std::string toString() const; + + void open(mail::callback &openCallback, + snapshot *restoreSnapshot, + mail::callback::folder &folderCallback) const; + }; + + // Special folders + + folder inboxFolder, hierarchyFolder; + + // Stuff gets done by creating a task object, and pushing it into + // the tasks queue (see below). The first task in the queue gets + // its doit() method called. doit() receives the timeout parameter + // from mbox::handler(), which it can adjust, if necessary. + // + // The doit() method should invoke done() when its done, otherwise + // it can simply exit, and be re-invoked the next time the handler + // is invoked. + // + // The task's disconnected() method gets invoked if the mbox + // object is destroyed in mid-stream, or if there's a fatal timeout + // trying to lock the mboxAccount file. disconnected() should NOT invoke + // done(), because that's going to happen anyway as soon as + // disconnected() wraps up. disconnected() is present so that + // the task can invoke any application callbacks the task is + // responsible for. + + class task { + + protected: + mbox &mboxAccount; + + public: + task(mbox &mboxAccount); + virtual ~task(); + + virtual void doit(int &timeout)=0; + virtual void disconnected()=0; + + virtual void done(); + }; + + // TimedTask subclasses from task, to provide a task with a timeout. + // + // Subclasses should implement bool doit(). bool doit() should return + // true if the task succeeded, or failed. If the task is deferred, + // the subclass's doit() method should return false. + // + // The subclass should call either fail() or success(), which invokes + // the callback's method, then invokes done(). + // + // The timedOut() method invokes the callback's fail method, if this + // tasks times out. The subclass can override this method to provide + // some value-added functionality. + // + + class TimedTask : public mbox::task { + + time_t timeout; + time_t nextTry; + + protected: + mail::callback &callback; + public: + TimedTask(mbox &mboxAccount, + mail::callback &callbackArg, int timeoutArg=60); + ~TimedTask(); + + void doit(int &timeout); + + void fail(std::string errmsg); + virtual bool doit()=0; + virtual void timedOut(); + virtual void disconnected(); + }; + + // This is a wrapper for liblock's locking functions + + class lock { + struct ll_mail *ll; + + int fd; + + bool readOnlyLock; + + lock(); + + public: + lock(std::string filename); // File to create an mboxAccount lock for. + ~lock(); + + bool operator()(bool readOnly=false); + // Attempt to lock this mboxAccount file, via liblock. + // Set readOnly to true if a read-only lock is permissible. + + int getfd() const { return fd; } + // Success - return the file descriptor of locked file. + + bool readOnly() const { return readOnlyLock; } + // Indicator whether the file was locked in read-only mode. + + + lock *copy(); + // Copy this lock structure to another object, used for + // optimizing consecutive parallel locks. + + }; + + class StatusTask; + class sighandler; + class OpenTask; + class CheckNewMailTask; + + class LockTask; + class GenericReadTask; + class GenericGetMessageTask; + class ExpungeTask; + + class MultiLock; + class MultiLockGenericMessageRead; + class MultiLockGenericAttributes; + class MultiLockRelease; + class RenameTask; + + std::queue<task *> tasks; + + void installTask(task *); + + // The currently open folder. + + std::string currentFolder; + bool currentFolderReadOnly; // Folder opened in readonly mode. + + off_t folderSavedSize; + time_t folderSavedTimestamp; + // Detect if someone else modified the folder file. + + std::string multiLockFilename; + lock *multiLockLock; + + bool folderDirty; + // Set to true when message metadata has been modified, but the + // folder file has not been updated to reflect these changes. + + bool newMessages; + // Set when index is updated from folderMessageIndex, and new messages + // were found. + + //////////////////////////////////////////////////////// + // + // The actual parsed contents of the folder: + + struct mboxMessageIndex { + + off_t startingPos; // Starting offset + time_t internalDate; // Synthesized message arrival date + mboxMagicTag tag; // The synthesized UID. + }; + + mail::keywords::Hashtable keywordHashtable; + std::vector<mboxMessageIndex> folderMessageIndex; // The folder + + std::map<std::string, size_t> uidmap; // Look up index from uid. + + // Generic interface caches the temporary file with the most recently + // opened message: + + std::string cachedMessageUid; // The cached uid + struct rfc2045 *cachedMessageRfcp; + FILE *cachedMessageFp; + + void resetFolder(); + + mail::callback::folder *currentFolderCallback; + + // The application sees the following index: + + std::vector<mail::messageInfo> index; + + // Update the mboxAccount file. This is where everything happens. + + bool scan(file &scanFile, + file *saveFile, + bool reopening, + std::set<std::string> *deleteMsgs, + bool rewrite, + mail::callback *progress); + + void checkNewMail(); + +public: + friend class folder; + friend class task; + friend class TimedTask; + friend class StatusTask; + friend class OpenTask; + friend class CheckNewMailTask; + friend class RenameTask; + + friend class LockTask; + friend class GenericReadTask; + friend class GenericGetMessageTask; + friend class ExpungeTask; + friend class MultiLock; + friend class MultiLockGenericMessageRead; + friend class MultiLockGenericAttributes; + friend class MultiLockRelease; + + mbox(bool magicInboxArg, + std::string folderRootArg, mail::callback &callback, + mail::callback::disconnect &disconnect_callback); + ~mbox(); + + void logout(mail::callback &callback); + void checkNewMail(mail::callback &callback); + + bool hasCapability(std::string capability); + std::string getCapability(std::string capability); + + mail::folder *folderFromString(std::string); + + void readTopLevelFolders(mail::callback::folderList &callback1, + mail::callback &callback2); + + void findFolder(std::string folder, + mail::callback::folderList &callback1, + mail::callback &callback2); + std::string translatePath(std::string path); + + static std::string translatePathCommon(std::string path, + const char *verbotten, + const char *sep); + + void readMessageAttributes(const std::vector<size_t> &messages, + MessageAttributes attributes, + mail::callback::message &callback); + + void readMessageContent(const std::vector<size_t> &messages, + bool peek, + enum mail::readMode readType, + mail::callback::message &callback); + + void readMessageContent(size_t messageNum, + bool peek, + const mimestruct &msginfo, + enum mail::readMode readType, + mail::callback::message &callback); + + void readMessageContentDecoded(size_t messageNum, + bool peek, + const mimestruct &msginfo, + mail::callback::message &callback); + + size_t getFolderIndexSize(); + mail::messageInfo getFolderIndexInfo(size_t); + + void saveFolderIndexInfo(size_t, + const mail::messageInfo &, + mail::callback &); + + void updateFolderIndexFlags(const std::vector<size_t> &messages, + bool doFlip, + bool enableDisable, + const mail::messageInfo &flags, + mail::callback &callback); + + void updateFolderIndexInfo(mail::callback &); + + + void updateKeywords(const std::vector<size_t> &messages, + const std::set<std::string> &keywords, + bool setOrChange, + // false: set, true: see changeTo + bool changeTo, + callback &cb); + void getFolderKeywordInfo(size_t, std::set<std::string> &); + + bool genericProcessKeyword(size_t messageNumber, + generic::updateKeywordHelper &helper); + + void removeMessages(const std::vector<size_t> &messages, + callback &cb); + + void copyMessagesTo(const std::vector<size_t> &messages, + mail::folder *copyTo, + mail::callback &callback); + + void searchMessages(const class mail::searchParams &searchInfo, + class mail::searchCallback &callback); + + bool verifyUid(std::string uid, size_t &messageNumber, + mail::callback &callback); + + + void genericMessageRead(std::string uid, + size_t messageNumber, + bool peek, + mail::readMode, + mail::callback::message &callback); + + void genericMessageSize(std::string uid, + size_t messageNumber, + mail::callback::message &callback); + + void genericGetMessageFd(std::string uid, + size_t messageNumber, + bool peek, + int &fdRet, + mail::callback &callback); + + void genericGetMessageStruct(std::string uid, + size_t messageNumber, + struct rfc2045 *&structRet, + mail::callback &callback); + + bool genericCachedUid(std::string uid); + + void genericGetMessageFdStruct(std::string uid, + size_t messageNumber, + bool peek, + int &fdRet, + struct rfc2045 *&structret, + mail::callback &callback); + void genericMarkRead(size_t messageNumber); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/mboxadd.C b/libmail/mboxadd.C new file mode 100644 index 0000000..7278da2 --- /dev/null +++ b/libmail/mboxadd.C @@ -0,0 +1,243 @@ +/* +** Copyright 2002-2006, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "mboxadd.H" +#include "mboxsighandler.H" +#include "file.H" + +#include <errno.h> +#include <pwd.h> +#include <time.h> + +#include <sys/types.h> +#include <sys/stat.h> + +using namespace std; + +mail::mbox::folder::add::add(mail::mbox &mboxArg, string pathArg, + mail::callback &callbackArg) + : mail::addMessage(&mboxArg), path(pathArg), + callback(callbackArg), + fp(tmpfile()), + mboxAccount(&mboxArg) +{ +} + +mail::mbox::folder::add::~add() +{ + if (fp) + fclose(fp); +} + +void mail::mbox::folder::add::saveMessageContents(string msg) +{ + if (*(msg.c_str()) == 0) + return; + + if (fp) + if (fwrite(&msg[0], msg.size(), 1, fp) != 1) + ; // Ignore gcc warning +} + +void mail::mbox::folder::add::fail(string msg) +{ + callback.fail(msg); + delete this; +} + +void mail::mbox::folder::add::success(string msg) +{ + callback.success(msg); + delete this; +} + +void mail::mbox::folder::add::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + callback.reportProgress(bytesCompleted, bytesEstimatedTotal, + messagesCompleted, messagesEstimatedTotal); +} + +void mail::mbox::folder::add::go() +{ + if (!fp) + fail(strerror(errno)); + + if (mboxAccount.isDestroyed()) + { + fail("Server connection aborted"); + return; + } + + try { + if (path == mboxAccount->currentFolder) + { + mboxAccount->installTask(new LockCurrentFolder( *this )); + } + else + { + mboxAccount->installTask(new LockCurrentFolder( *this, + path)); + } + } catch (...) { + fail("An exception occured while adding message."); + return; + } +} + +// +// Folder's now locked. +// + +void mail::mbox::folder::add::copyTo(mail::file &file) +{ + struct stat st; + + mail::mbox::sighandler updating(fileno(static_cast<FILE *>(file))); + + try { + + // Make sure the mboxAccount file ends with a trailing newline + + if (fstat(fileno(static_cast<FILE *>(file)), &st) < 0) + { + fail(strerror(errno)); + return; + } + + int ch='\n'; + + if (st.st_size > 0) + { + if (fseek(file, -1, SEEK_END) < 0 || + (ch=getc(file)) == EOF) + { + fail(strerror(errno)); + return; + } + } + + if (fseek(file, 0L, SEEK_END) < 0 || + fseek(fp, 0L, SEEK_SET) < 0) + { + fail(strerror(errno)); + return; + } + + if (ch != '\n') + putc('\n', file); + + mail::file f(fileno(fp), "w+"); + + messageInfo.uid= mail::mboxMagicTag() + .getMessageInfo().uid; + + string hdr=mail::mboxMagicTag(messageInfo.uid, + messageInfo, + + mail::keywords::Message() + // KEYWORDS + + ).toString(); + + struct passwd *pw; + pw=getpwuid(getuid()); + + fprintf(file, "From %s %s%s\n", + pw ? pw->pw_name:"nobody", + ctime(&messageDate), + hdr.c_str()); + + size_t bytesDone=0; + size_t bytesNextReport=0; + + while (!feof(f)) + { + string l=f.getline(); + + if (l.size() == 0 && feof(f)) + break; + + const char *p=l.c_str(); + + while (*p == '>') + p++; + + if (strncmp(p, "From ", 5) == 0) + l=">" + l; + + l += "\n"; + + if (fwrite(&l[0], l.size(), 1, file) != 1) + ; // Ignore gcc warning + + bytesDone += l.size(); + + if (bytesDone >= bytesNextReport) + { + bytesNextReport=bytesDone + BUFSIZ; + callback.reportProgress(bytesDone, 0, 0, 1); + } + } + + if (fflush(file) < 0 || ferror(file)) + { + updating.rollback(); + fail(strerror(errno)); + return; + } + + callback.reportProgress(bytesDone, bytesDone, 1, 1); + updating.block(); + } catch (...) { + updating.rollback(); + fail("An exception occured while adding message."); + return; + } + + try { + fclose(fp); + fp=NULL; + success("OK"); + } catch (...) { + delete this; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +/////////////////////////////////////////////////////////////////////////// + +mail::mbox::folder::add::LockCurrentFolder +::LockCurrentFolder(add &meArg, string pathArg) + : LockTask(*meArg.mboxAccount, meArg, pathArg), me(meArg) +{ +} + +mail::mbox::folder::add::LockCurrentFolder::~LockCurrentFolder() +{ +} + + +bool mail::mbox::folder::add::LockCurrentFolder::locked(mail::file + &lockedFile) +{ + // If the current folder is opened in read-only mode, this is a no-go. + + if (me.path == me.mboxAccount->currentFolder && mboxAccount.currentFolderReadOnly) + { + me.fail("Folder opened in read-only mode."); + done(); + return true; + } + + me.copyTo(lockedFile); + done(); + return true; +} + diff --git a/libmail/mboxadd.H b/libmail/mboxadd.H new file mode 100644 index 0000000..7d4a24f --- /dev/null +++ b/libmail/mboxadd.H @@ -0,0 +1,71 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_mboxfolderadd_H +#define libmail_mboxfolderadd_H + +#include "libmail_config.h" +#include "mbox.H" +#include "mboxlock.H" +#include "addmessage.H" + +#include <stdio.h> + +LIBMAIL_START + +/////////////////////////////////////////////////////////////////////////// +// +// Add a message to a folder. + +class file; + +class mbox::folder::add : public addMessage, + public mail::callback { + + std::string path; + mail::callback &callback; + FILE *fp; + + ptr<mbox> mboxAccount; + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + + // + // Lock whatever folder's being added to. + // + + class LockCurrentFolder : public mbox::LockTask { + + add &me; + + public: + LockCurrentFolder(add &meArg, std::string pathArg=""); + ~LockCurrentFolder(); + + bool locked(file &lockedFile); + }; + +public: + add(mbox &mboxArg, std::string pathArg, + mail::callback &callbackArg); + ~add(); + + void saveMessageContents(std::string); + void go(); + void fail(std::string); + + void success(std::string); + + void copyTo(file &file); +}; + +LIBMAIL_END + +#endif + diff --git a/libmail/mboxexpunge.C b/libmail/mboxexpunge.C new file mode 100644 index 0000000..da14077 --- /dev/null +++ b/libmail/mboxexpunge.C @@ -0,0 +1,205 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include <cstring> + +#include "mbox.H" +#include "mboxexpunge.H" +#include "expungelist.H" +#include "file.H" + +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> + +using namespace std; + +mail::mbox::ExpungeTask::ExpungeTask(mail::mbox &mboxAccount, + mail::callback &callback, + bool purgeDeletedArg, + const vector<size_t> *listPtr) + : LockTask(mboxAccount, callback), + purgeDeleted(purgeDeletedArg) +{ + if (purgeDeleted) + { + if (!listPtr) + { + // Default - delete msgs marked for deletion. + + vector<mail::messageInfo>::iterator b, e; + + b=mboxAccount.index.begin(); + e=mboxAccount.index.end(); + + while (b != e) + { + if (b->deleted) + deleteList.insert(b->uid); + b++; + } + } + else // Explicitly delete these msgs only + { + vector<size_t>::const_iterator b, e; + b=listPtr->begin(); + e=listPtr->end(); + + while (b != e) + { + size_t n= *b++; + + if (n < mboxAccount.index.size()) + deleteList.insert(mboxAccount.index[n] + .uid); + } + } + } +} + +mail::mbox::ExpungeTask::~ExpungeTask() +{ +} + +// +// Override LockTask::locked(). Create a scratch file, then call the +// superclass, which invokes locked(mail::file), which runs mboxAccount.scan() to +// do the actual deed. +// +// After the mailbox is purged, the folder index is compared to the real +// folder index, and removed messages are removed from the index, the any +// new messages are added. +// + +bool mail::mbox::ExpungeTask::locked(mail::mbox::lock &mlock, string path) +{ + struct stat stat_buf; + + // Rewrite the file to the following scratch file: + + string scratchFile=path + "~"; + + if (mboxAccount.currentFolderReadOnly) + { + callback.success("Folder opened in read-only mode."); + done(); + return true; + } + + mail::ptr<mail::mbox> monitor(&mboxAccount); + + + int f= ::open(scratchFile.c_str(), O_WRONLY|O_CREAT|O_TRUNC, 0600); + + if (f < 0) + { + fail(strerror(errno)); + return true; + } + + if (fstat(mlock.getfd(), &stat_buf) < 0 || + chmod(scratchFile.c_str(), stat_buf.st_mode & 0777) < 0) + { + close(f); + unlink(scratchFile.c_str()); + fail(strerror(errno)); + return true; + } + + bool rc; + + bool flag; + + try { + mail::file wf(f, "w"); + + close(f); + f= -1; + + saveFile= &wf; + ok= &flag; + flag=false; + + rc= LockTask::locked(mlock, path); + } catch (...) { + if (f >= 0) + close(f); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + if (rc) + { + if (flag) + { + rename(scratchFile.c_str(), path.c_str()); + + if (purgeDeleted && !monitor.isDestroyed()) + { + size_t i=mboxAccount.index.size(); + + mail::expungeList removedList; + + while (mboxAccount.index.size() > 0 && i > 0) + { + if (mboxAccount.uidmap.count(mboxAccount.index[--i] + .uid) > 0) + continue; + + mboxAccount.index + .erase(mboxAccount.index + .begin()+i); + + removedList << i; + } + + removedList >> + mboxAccount.currentFolderCallback; + } + + // Now, check for new mail. + + if (!monitor.isDestroyed()) + mboxAccount.checkNewMail(); + + if (!monitor.isDestroyed()) + { + mboxAccount.folderDirty=false; + if (mboxAccount.newMessages) + { + mboxAccount.newMessages=false; + if (mboxAccount.currentFolderCallback) + mboxAccount.currentFolderCallback + ->newMessages(); + } + } + + callback.success("Folder expunged."); + done(); + } + else + unlink(scratchFile.c_str()); + } + return rc; +} + +bool mail::mbox::ExpungeTask::locked(mail::file &file) +{ + + if (!mboxAccount.scan(file, saveFile, true, + purgeDeleted ? &deleteList:NULL, + true, + &callback)) + { + fail(strerror(errno)); + return true; + } + + *ok=true; + + return true; +} diff --git a/libmail/mboxexpunge.H b/libmail/mboxexpunge.H new file mode 100644 index 0000000..0343923 --- /dev/null +++ b/libmail/mboxexpunge.H @@ -0,0 +1,43 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_mboxexpunge_H + +#include "mbox.H" +#include "mboxlock.H" + +#include <set> +#include <string> + +LIBMAIL_START + +class file; + +//////////////////////////////////////////////////////////////////////////// +// +// Expunge messages now. + +class mbox::ExpungeTask : public mbox::LockTask { + + file *saveFile; + bool *ok; + + bool purgeDeleted; + + std::set<std::string> deleteList; + +public: + ExpungeTask(mbox &mboxAccount, mail::callback &callback, + bool purgeDeletedArg, + const std::vector<size_t> *deleteList); + ~ExpungeTask(); + + bool locked(mbox::lock &mlock, std::string path); + bool locked(file &file); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/mboxfolder.C b/libmail/mboxfolder.C new file mode 100644 index 0000000..d75f15b --- /dev/null +++ b/libmail/mboxfolder.C @@ -0,0 +1,860 @@ +/* +** Copyright 2002-2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "mbox.H" +#include "misc.H" +#include "mboxlock.H" +#include "mboxopen.H" +#include "mboxexpunge.H" +#include "mboxmagictag.H" +#include "mboxadd.H" +#include "file.H" +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <errno.h> +#include "unicode/unicode.h" + +#if HAVE_DIRENT_H +# include <dirent.h> +# define NAMLEN(dirent) strlen((dirent)->d_name) +#else +# define dirent direct +# define NAMLEN(dirent) (dirent)->d_namlen +# if HAVE_SYS_NDIR_H +# include <sys/ndir.h> +# endif +# if HAVE_SYS_DIR_H +# include <sys/dir.h> +# endif +# if HAVE_NDIR_H +# include <ndir.h> +# endif +#endif + +#include <vector> + +using namespace std; + +mail::mbox::folder::folder(string pathArg, + mail::mbox &mboxArg) + : mail::folder(&mboxArg), path(pathArg), + name(defaultName(pathArg)), mboxAccount(mboxArg), + saveHasMessages(false), + saveHasFolders(false) +{ +} + +mail::mbox::folder::folder(const mail::mbox::folder &f) + : mail::folder(&f.mboxAccount), path(f.path), name(f.name), + mboxAccount(f.mboxAccount), + saveHasMessages(false), + saveHasFolders(false) +{ +} + +mail::mbox::folder::~folder() +{ +} + +string mail::mbox::folder::defaultName(string path) +{ + string::iterator b, e, c; + + b=path.begin(); + e=path.end(); + c=b; + + while (b != e) + if (*b++ == '/') + c=b; + + path=string(c, e); + + char *p=libmail_u_convert_tobuf(path.c_str(), + unicode_x_imap_modutf7, + unicode_default_chset(), + NULL); + + if (p) + { + try { + path=p; + free(p); + } catch (...) { + free(p); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + } + + return path; +} + +void mail::mbox::folder::sameServerAsHelperFunc() const +{ +} + +string mail::mbox::folder::getName() const +{ + return name; +} + +string mail::mbox::folder::getPath() const +{ + return path; +} + +bool mail::mbox::folder::hasMessages() const +{ + struct stat stat_buf; + + return saveHasMessages || path == "INBOX" || + (stat(path.c_str(), &stat_buf) == 0 + && !S_ISDIR(stat_buf.st_mode)); +} + +bool mail::mbox::folder::hasSubFolders() const +{ + return saveHasFolders || !hasMessages(); +} + +bool mail::mbox::folder::isParentOf(string pathArg) const +{ + return pathArg.size() > path.size() && + pathArg.substr(0, path.size() + 1 ) == (path + "/"); +} + +void mail::mbox::folder::hasMessages(bool arg) +{ + saveHasMessages=arg; +} + +void mail::mbox::folder::hasSubFolders(bool arg) +{ + saveHasFolders=arg; +} + +/////////////////////////////////////////////////////////////////////////// +// + +class mail::mbox::StatusTask : public mail::mbox::TimedTask { + + string folder; + + mail::callback::folderInfo &info; + + bool count(int fd); + +public: + StatusTask(string folderArg, mail::mbox &mboxAccount, + mail::callback &callbackArg, + mail::callback::folderInfo &infoArg); + ~StatusTask(); + + bool doit(); +}; + +mail::mbox::StatusTask::StatusTask(string folderArg, mail::mbox &mboxAccount, + mail::callback &callback, + mail::callback::folderInfo &infoArg) + : TimedTask(mboxAccount, callback), folder(folderArg), info(infoArg) +{ +} + +mail::mbox::StatusTask::~StatusTask() +{ +} + +bool mail::mbox::StatusTask::doit() +{ + if (folder == mboxAccount.currentFolder) + { + info.messageCount=mboxAccount.index.size(); + + size_t i; + + for (i=0; i<info.messageCount; i++) + if (mboxAccount.index[i].unread) + info.unreadCount++; + info.success(); + callback.success("OK"); + done(); + return true; + } + + if (info.fastInfo) // mboxes are expensive + { + callback.success("OK"); + done(); + return true; + } + + if (folder == "INBOX") + { + int fd=::open(mboxAccount.inboxSpoolPath.c_str(), + O_RDWR|O_CREAT, 0600); + + if (fd >= 0) + close(fd); + + fd=::open(mboxAccount.inboxMailboxPath.c_str(), O_RDWR|O_CREAT, 0600); + + if (fd >= 0) + close(fd); + + mail::mbox::lock inbox_lock(mboxAccount.inboxSpoolPath); + mail::mbox::lock mailbox_lock(mboxAccount.inboxMailboxPath); + + if (!inbox_lock(true) || !count(inbox_lock.getfd())) + { + if (errno != EAGAIN && errno != EEXIST + && errno != ENOENT) + { + fail(string("INBOX: ") + strerror(errno)); + return true; + } + } + + if (!mailbox_lock(true) || !count(mailbox_lock.getfd())) + { + if (errno != EAGAIN && errno != EEXIST + && errno != ENOENT) + { + fail(string("INBOX: ") + strerror(errno)); + return true; + } + } + } + else + { + mail::mbox::lock mailbox_lock(folder); + + if (!mailbox_lock(true) || !count(mailbox_lock.getfd())) + { + if (errno != EAGAIN && errno != EEXIST) + { + fail(folder + ": " + strerror(errno)); + return true; + } + + return false; + } + } + + info.success(); + callback.success("OK"); + done(); + return true; +} + +bool mail::mbox::StatusTask::count(int fd) +{ + mail::file fp(fd, "r"); + + if (!fp) + return false; + + bool firstLine=true; + + while (!feof(fp)) + { + string l=fp.getline(); + + if (l.size() == 0) + continue; + + if (strncmp(l.c_str(), "From ", 5) == 0) + { + firstLine=false; + info.messageCount++; + info.unreadCount++; + + mail::mboxMagicTag tag(fp.getline(), + mboxAccount.keywordHashtable); + + if (!tag.good()) + continue; + + mail::messageInfo msginfo=tag.getMessageInfo(); + + if (!msginfo.unread) + --info.unreadCount; + } + + if (firstLine) + break; // Not an mboxAccount file. + } + return true; +} + +void mail::mbox::folder::getParentFolder(callback::folderList &callback1, + callback &callback2) const +{ + if (isDestroyed(callback2)) + return; + + size_t n; + + if (path == "INBOX" || path == mboxAccount.rootPath || + (n=path.rfind('/')) == std::string::npos) + { + mail::mbox::folder dummy("", mboxAccount); + + vector<const mail::folder *> array; + + array.push_back(&dummy); + callback1.success(array); + callback2.success("OK"); + return; + } + + mboxAccount.findFolder(path.substr(0, n), callback1, callback2); +} + +void mail::mbox::folder::readFolderInfo( mail::callback::folderInfo + &callback1, + mail::callback &callback2) const +{ + if (isDestroyed(callback2)) + return; + + callback1.messageCount=0; + callback1.unreadCount=0; + + if (path.size() == 0) + { + callback1.success(); + callback2.success("OK"); + return; + } + + mboxAccount.installTask(new mail::mbox::StatusTask(path, mboxAccount, callback2, + callback1)); +} + +///////////////////////////////////////////////////////////////////////////// + +void mail::mbox::folder::readSubFolders( mail::callback::folderList &callback1, + mail::callback &callback2) const +{ + if (isDestroyed(callback2)) + return; + + if (path.size() == 0) + { + mboxAccount.readTopLevelFolders(callback1, callback2); + return; + } + + vector<folder *> folderList; + + vector<folder *>::iterator b, e; + + DIR *dirp=opendir(path.c_str()); + + try { + struct dirent *de; + + while (dirp && (de=readdir(dirp)) != NULL) + { + char *p; + + if (strcmp(de->d_name, "..") == 0 || + strcmp(de->d_name, ".") == 0 || + ((p=strrchr(de->d_name, '~')) && p[1] == 0)) + continue; + + for (p=de->d_name; *p; p++) + if (*p == '.') + if (strcmp(p, ".lock") == 0 || + strncmp(p, ".lock.", 6) == 0) + break; + + if (*p) + continue; + + string name=de->d_name; + + string fpath=path + "/" + name; + + folder *f=new folder(fpath, mboxAccount); + + if (!f) + LIBMAIL_THROW("Out of memory."); + + try { + folderList.push_back(f); + } catch (...) { + delete f; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + } + + vector<const mail::folder *> folders; + + b=folderList.begin(); + e=folderList.end(); + + while (b != e) + folders.push_back(*b++); + + callback1.success(folders); + + b=folderList.begin(); + e=folderList.end(); + + while (b != e) + delete *b++; + + if (dirp) + closedir(dirp); + } catch (...) { + b=folderList.begin(); + e=folderList.end(); + + while (b != e) + delete *b++; + + if (dirp) + closedir(dirp); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + callback2.success("OK"); +} + +mail::addMessage *mail::mbox::folder::addMessage(mail::callback &callback) + const +{ + mail::mbox::folder::add *f + =new mail::mbox::folder::add(mboxAccount, path, callback); + + if (!f) + { + callback.fail(path + ": " + strerror(errno)); + return NULL; + } + + return f; +} + +void mail::mbox::folder::createSubFolder(string name, + bool isDirectory, + mail::callback::folderList &callback1, + mail::callback &callback2) const +{ + string fpath; + + char *p=libmail_u_convert_tobuf(name.c_str(), unicode_default_chset(), + unicode_x_imap_modutf7 " ./~:", NULL); + + if (!p) + LIBMAIL_THROW("Out of memory."); + + try { + fpath=path + "/" + p; + free(p); + } catch (...) { + free(p); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + if (isDirectory) { + if (mkdir(fpath.c_str(), 0700) < 0) + { + callback2.fail(fpath + ": " + strerror(errno)); + return; + } + } else { + int fd= ::open(fpath.c_str(), O_RDWR|O_CREAT|O_EXCL, 0600); + + if (fd < 0) + { + callback2.fail(fpath + ": " + strerror(errno)); + return; + } + close(fd); + } + + folder newFolder(fpath, mboxAccount); + + vector<const mail::folder *> folderList; + + folderList.push_back(&newFolder); + callback1.success(folderList); + callback2.success("OK"); +} + +void mail::mbox::folder::create(bool isDirectory, + mail::callback &callback) const +{ + if (isDirectory) { + if (mkdir(path.c_str(), 0700) < 0) + { + callback.fail(path + ": " + strerror(errno)); + return; + } + } else { + int fd= ::open(path.c_str(), O_RDWR|O_CREAT|O_EXCL, 0600); + + if (fd < 0) + { + callback.fail(path + ": " + strerror(errno)); + return; + } + close(fd); + } + + callback.success("OK"); +} + +void mail::mbox::folder::destroy(mail::callback &callback, bool destroyDir) + const +{ + if (destroyDir) { + + // Clean up garbage first. + + DIR *dirp=opendir(path.c_str()); + + try { + struct dirent *de; + + while (dirp && (de=readdir(dirp)) != NULL) + { + const char *p=strrchr(de->d_name, '~'); + + if (p && p[1] == 0) // Temp file? + { + string s=path + "/" + + string((const char *) + de->d_name, p); + + struct stat stat_buf; + + if (stat(s.c_str(), &stat_buf) < 0) + { + s += "~"; + unlink(s.c_str()); + } + continue; + } + + for (p=de->d_name; *p; p++) + { + if (strncmp(p, ".lock.", 6) == 0) + { + string s=path + "/" + + de->d_name; + + struct stat stat_buf; + + if (stat(s.c_str(), &stat_buf) + == 0 && + stat_buf.st_mtime + 120 + < time(NULL)) + break; + } + + if (strcmp(p, ".lock") == 0) + { + string s=path + "/" + + string((const char *) + de->d_name, + p); + struct stat stat_buf; + + if (stat(s.c_str(), &stat_buf) + < 0) + break; + } + } + + if (*p) + { + string s=path + "/" + de->d_name; + + unlink(s.c_str()); + } + } + + if (dirp) + closedir(dirp); + } catch (...) { + if (dirp) + closedir(dirp); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + if (rmdir(path.c_str()) < 0) + { + callback.fail(path + ": " + strerror(errno)); + return; + } + } else { + if (unlink(path.c_str()) < 0) + { + callback.fail(path + ": " + strerror(errno)); + return; + } + } + + callback.success("OK"); +} + +class mail::mbox::RenameTask : public mail::mbox::LockTask { + + string filename; + string newpath; + string newname; + mail::callback::folderList &callback1; + +public: + RenameTask(mbox &mboxAccount, + mail::callback::folderList &callback1Arg, + mail::callback &callbackArg, + string filenameArg, + string newpathArg, + string newnameArg); + ~RenameTask(); + + bool locked(mail::mbox::lock &mlock, std::string path); + bool locked(mail::file &); +}; + +mail::mbox::RenameTask::RenameTask(mbox &mboxAccount, + mail::callback::folderList &callback1Arg, + mail::callback &callbackArg, + string filenameArg, + string newpathArg, + string newnameArg) + : LockTask(mboxAccount, callbackArg, filenameArg), + filename(filenameArg), + newpath(newpathArg), + newname(newnameArg), + callback1(callback1Arg) +{ +} + +mail::mbox::RenameTask::~RenameTask() +{ +} + +bool mail::mbox::RenameTask::locked(mail::mbox::lock &mlock, std::string path) +{ + mail::callback *volatile cb= &callback; + string msg="Folder renamed."; + + if (link(filename.c_str(), newpath.c_str()) < 0) + { + fail(strerror(errno)); + return true; + } + unlink(filename.c_str()); + + mail::mbox::folder newFolder(newpath, mboxAccount); + + vector<const mail::folder *> folders; + + folders.push_back(&newFolder); + callback1.success( folders ); + + try { + done(); + } catch (...) { + } + cb->success(msg); + return true; +} + +bool mail::mbox::RenameTask::locked(mail::file &) +{ + return true; +} + +void mail::mbox::folder::renameFolder(const mail::folder *newParent, + std::string newName, + mail::callback::folderList &callback1, + mail::callback &callback) const +{ + if (isDestroyed(callback)) + return; + + if (path == "INBOX") + { + callback.fail("Not implemented."); + return; + } + + if (mboxAccount.currentFolder.size() > 0) + { + if (mboxAccount.currentFolder == path || + (path + "/") == + mboxAccount.currentFolder.substr(0, path.size()+1)) + { + callback.fail("Cannot RENAME currently open folder."); + return; + } + } + + // The name of the folder is translated from the local charset + // to modified UTF-7 (Courier-IMAP compatibility), with the following + // blacklisted characters: + + string nameutf7=mail::iconvert::convert(newName, + unicode_default_chset(), + unicode_x_imap_modutf7 " ./~:"); + + string newpath=(newParent ? newParent->getPath() + "/": + string("")) + nameutf7; + + if (newpath.size() == 0 || newpath[0] != '/') + newpath=mboxAccount.rootPath + "/" + newpath; + + struct stat stat_buf; + + if (stat(path.c_str(), &stat_buf) == 0 && + S_ISDIR(stat_buf.st_mode)) + { + if (rename(path.c_str(), newpath.c_str()) < 0) + { + callback.fail(strerror(errno)); + return; + } + + mail::mbox::folder newFolder(newpath, mboxAccount); + + vector<const mail::folder *> folders; + + folders.push_back(&newFolder); + callback1.success( folders ); + callback.success("Mail folder renamed."); + return; + } + + mboxAccount.installTask(new mail::mbox::RenameTask(mboxAccount, + callback1, + callback, + path, newpath, + newName)); +} + +mail::folder *mail::mbox::folder::clone() const +{ + return new mail::mbox::folder(path, mboxAccount); +} + +string mail::mbox::folder::toString() const +{ + string s=""; + string::const_iterator b=name.begin(), e=name.end(); + + while (b != e) + { + if (*b == ':' || *b == '\\') + s += "\\"; + + s += *b++; + } + + // If path is in the home directory, drop $HOME + + string p=path; + + if (!isDestroyed()) + { + string h=mboxAccount.rootPath; + + if (p == h) + p=""; + else + { + h += "/"; + if (p.size() > h.size() && p.substr(0, h.size()) == h) + p=p.substr(h.size()); + } + } + + return s + ":" + p; +} + +void mail::mbox::folder::open(mail::callback &openCallback, + snapshot *restoreSnapshot, + mail::callback::folder &folderCallback) const +{ + if (mboxAccount.currentFolder.size() == 0 && + mboxAccount.folderDirty) + { + closeCallback *cc=new closeCallback(&mboxAccount, + openCallback, + folderCallback, + path); + + if (!cc) + { + openCallback.fail("Out of memory."); + return; + } + + try { + mboxAccount + .installTask(new mail::mbox + ::ExpungeTask(mboxAccount, *cc, + false, NULL)); + } catch (...) { + delete cc; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + return; + } + + mboxAccount.installTask(new mail::mbox::OpenTask(mboxAccount, path, openCallback, + &folderCallback)); +} + +mail::mbox::folder::closeCallback::closeCallback(mail::mbox *origmboxArg, + mail::callback + &origCallbackArg, + mail::callback::folder + &origFolderCallbackarg, + string origPathArg) + : origmbox(origmboxArg), origCallback(origCallbackArg), + origFolderCallback(origFolderCallbackarg), + origPath(origPathArg) +{ +} + +mail::mbox::folder::closeCallback::~closeCallback() +{ +} + +void mail::mbox::folder::closeCallback::success(string msg) +{ + if (origmbox.isDestroyed()) + { + origCallback.success(msg); + delete this; + } + + try { + origmbox->installTask(new OpenTask(*origmbox, + origPath, + origCallback, + &origFolderCallback)); + delete this; + } catch (...) { + delete this; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +void mail::mbox::folder::closeCallback::fail(string msg) +{ + origCallback.fail(msg); + delete this; +} diff --git a/libmail/mboxgetmessage.C b/libmail/mboxgetmessage.C new file mode 100644 index 0000000..dfd775b --- /dev/null +++ b/libmail/mboxgetmessage.C @@ -0,0 +1,195 @@ +/* +** Copyright 2002-2006, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" + +#include "mboxgetmessage.H" +#include "file.H" +#include "rfc2045/rfc2045.h" + +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> + +using namespace std; + +mail::mbox::GenericGetMessageTask::GenericGetMessageTask(mail::mbox &mboxAccount, + mail::callback + &callbackArg, + string uidArg, + size_t + messageNumberArg, + bool peekArg, + int *fdretArg, + struct rfc2045 + **structretArg) + : LockTask(mboxAccount, callbackArg), + uid(uidArg), + messageNumber(messageNumberArg), + peek(peekArg), + fdret(fdretArg), + structret(structretArg), + + tempFp(NULL), + tempStruct(NULL) +{ +} + +mail::mbox::GenericGetMessageTask::~GenericGetMessageTask() +{ + // tempFp/tempStruct should be null, if all's well. If not, clean up + // after outselves. + + if (tempFp) + fclose(tempFp); + + if (tempStruct) + rfc2045_free(tempStruct); +} + +bool mail::mbox::GenericGetMessageTask::locked(mail::file &file) +{ + if (!mboxAccount.verifyUid(uid, messageNumber, callback)) + { + done(); + return true; + } + + mail::ptr<mail::mbox> me= &mboxAccount; + + size_t realMsgNum=mboxAccount.uidmap.find(uid)->second; + + off_t startingPos=mboxAccount.folderMessageIndex[realMsgNum] + .startingPos; + + if (fseek(file, startingPos, SEEK_SET) < 0 || + (tempFp=tmpfile()) == NULL || + (tempStruct=rfc2045_alloc()) == NULL) + { + fail(strerror(errno)); + return true; + } + + off_t endingPos=0; + + if (realMsgNum + 1 >= mboxAccount.folderMessageIndex.size()) + { + struct stat stat_buf; + + if (fstat(fileno(static_cast<FILE *>(file)), &stat_buf) < 0) + endingPos=stat_buf.st_size; + } + else + endingPos=mboxAccount.folderMessageIndex[realMsgNum+1] + .startingPos; + + if (endingPos >= startingPos) + endingPos -= startingPos; + else + endingPos=0; + + off_t nextUpdatePos=startingPos; + + bool firstLine=true; + + while (!feof(file) && !me.isDestroyed()) + { + off_t pos=file.tell(); + + // Progress update every 10K + if (pos >= nextUpdatePos) + { + nextUpdatePos=pos + 10000; + + callback.reportProgress(pos - startingPos, endingPos, + 0, 1); + } + + string line=file.getline(); + + if (line.size() == 0 && feof(file)) + break; + + if (strncmp(line.c_str(), "From ", 5) == 0) + { + if (firstLine) + continue; + + break; + } + + if (firstLine) + { + firstLine=false; + + if (mail::mboxMagicTag(line, + mboxAccount.keywordHashtable) + .good()) + continue; // Ignore magic tag line. + } + + const char *p=line.c_str(); + + if (*p == '>') + { + while (*p == '>') + p++; + + if (strncmp(p, "From ", 5) == 0) + line=line.substr(1); + } + + line += "\n"; + + if (fwrite(&line[0], line.size(), 1, tempFp) != 1) + ; // Ignore gcc warning + + rfc2045_parse(tempStruct, &line[0], line.size()); + } + + if (fflush(tempFp) < 0 || ferror(tempFp) < 0) + { + fail(strerror(errno)); + return true; + } + + mboxAccount.cachedMessageUid=""; + if (mboxAccount.cachedMessageFp) + fclose(mboxAccount.cachedMessageFp); + if (mboxAccount.cachedMessageRfcp) + rfc2045_free(mboxAccount.cachedMessageRfcp); + + mboxAccount.cachedMessageFp=NULL; + mboxAccount.cachedMessageRfcp=NULL; + + mboxAccount.cachedMessageUid=uid; + mboxAccount.cachedMessageFp=tempFp; + mboxAccount.cachedMessageRfcp=tempStruct; + + fcntl(fileno(tempFp), F_SETFD, FD_CLOEXEC); + + tempFp=NULL; + tempStruct=NULL; + + if (fdret) + *fdret=fileno(mboxAccount.cachedMessageFp); + if (structret) + *structret=mboxAccount.cachedMessageRfcp; + + if (mboxAccount.currentFolderCallback && !peek && + mboxAccount.index[messageNumber].unread) + { + mboxAccount.index[messageNumber].unread=false; + mboxAccount.folderDirty=true; + mboxAccount.currentFolderCallback->messageChanged(messageNumber); + } + + callback.success("OK"); + done(); + return true; +} + diff --git a/libmail/mboxgetmessage.H b/libmail/mboxgetmessage.H new file mode 100644 index 0000000..7e3fa6b --- /dev/null +++ b/libmail/mboxgetmessage.H @@ -0,0 +1,49 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_mboxgetmessage_H +#define libmail_mboxgetmessage_H + +#include "libmail_config.h" +#include "mbox.H" +#include "mboxlock.H" + +struct rfc2045; + +LIBMAIL_START + +class file; + +//////////////////////////////////////////////////////////////////////////// +// +// Implement generic's genericGetMessage[Fd][Struct] functions + +class mbox::GenericGetMessageTask : public mbox::LockTask { + + std::string uid; + size_t messageNumber; + bool peek; + int *fdret; + struct rfc2045 **structret; + + FILE *tempFp; + struct rfc2045 *tempStruct; + +public: + GenericGetMessageTask(mbox &mboxAccount, + mail::callback &callbackArg, + std::string uidArg, + size_t messageNumberArg, + bool peekArg, + int *fdretArg, + struct rfc2045 **structretArg); + ~GenericGetMessageTask(); + + bool locked(file &file); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/mboxlock.C b/libmail/mboxlock.C new file mode 100644 index 0000000..46687a1 --- /dev/null +++ b/libmail/mboxlock.C @@ -0,0 +1,174 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "mboxlock.H" +#include "mboxopen.H" +#include "file.H" +#include <cstring> + +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> + +using namespace std; + +// +// Stub callback used for the OpenTask that updates the internal folder +// index. When OpenTask completes this object goes back on the queue. +// + + +mail::mbox::LockTask::reopenCallback::reopenCallback(mail::mbox::LockTask *t) + : task(t) +{ +} + +mail::mbox::LockTask::reopenCallback::~reopenCallback() +{ +} + +void mail::mbox::LockTask::reopenCallback::success(string msg) +{ + try { + task->mboxAccount.installTask(task); + } catch (...) { + delete task; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +void mail::mbox::LockTask::reopenCallback::fail(string msg) +{ + task->fail(msg); +} + +//////////////////////////////////////////////////////////////////////// + +mail::mbox::LockTask::LockTask(mail::mbox &mboxAccount, + mail::callback &callbackArg, string filenameArg) + : TimedTask(mboxAccount, callbackArg), reopen(this), + filename(filenameArg) +{ +} + +mail::mbox::LockTask::~LockTask() +{ +} + +bool mail::mbox::LockTask::doit() +{ + for (;;) + { + string pathStr=filename; + bool ro=false; + + if (pathStr.size() == 0) + { + pathStr=mboxAccount.currentFolder; + ro=mboxAccount.currentFolderReadOnly; + } + + if (pathStr.size() == 0) + { + callback.success("Folder locked"); + done(); + return true; + } + + struct stat stat_buf; + + if (pathStr == "INBOX") + pathStr=mboxAccount.inboxMailboxPath; + + if (mboxAccount.multiLockLock && + mboxAccount.multiLockFilename == pathStr) + { + return locked( *mboxAccount.multiLockLock, pathStr); + } + + mail::mbox::lock mailbox_lock( pathStr ); + + if (!mailbox_lock(ro)) + { + // TODO: fail if EEXIST + + if (errno != EAGAIN && errno != EEXIST) + { + fail(pathStr + ": " + strerror(errno)); + return true; + } + return false; + } + + if (filename.size() > 0) + // Locked another folder, no need + // to check. + { + try { + return locked(mailbox_lock, pathStr); + } catch (...) { + fail("Exception caught while accessing folder."); + } + return true; + } + + if (fstat(mailbox_lock.getfd(), &stat_buf) < 0) + { + fail(pathStr + ": " + strerror(errno)); + return true; + } + + if (stat_buf.st_size != mboxAccount.folderSavedSize || + stat_buf.st_mtime != mboxAccount.folderSavedTimestamp) + break; + + try { + return locked(mailbox_lock, pathStr); + } catch (...) { + fail("Exception caught while accessing folder."); + } + return true; + } + + // Uh-oh, something happened to the folder. + + OpenTask *t=new OpenTask(mboxAccount, mboxAccount.currentFolder, reopen, NULL); + + if (!t) + { + fail(mboxAccount.currentFolder + ": " + strerror(errno)); + return true; + } + + try { + mboxAccount.tasks.pop(); + } catch (...) { + delete t; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + try { + mboxAccount.installTask(t); + } catch (...) { + fail("Exception caught while accessing folder."); + } + + return true; +} + +bool mail::mbox::LockTask::locked(mail::mbox::lock &mlock, string path) +{ + mail::file mboxFp(mlock.getfd(), + mlock.readOnly() ? "r":"r+"); + + if (!mboxFp) + { + fail(strerror(errno)); + return true; + } + + return (locked(mboxFp)); +} diff --git a/libmail/mboxlock.H b/libmail/mboxlock.H new file mode 100644 index 0000000..8f4dc8a --- /dev/null +++ b/libmail/mboxlock.H @@ -0,0 +1,67 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_mboxlock_H +#define libmail_mboxlock_H + +#include "libmail_config.h" +#include "mbox.H" + +//////////////////////////////////////////////////////////////////////// +// +// Convenient superclass that locks the mailbox file in a consistent +// state. First, a mailbox lock is obtained. After the mailbox is locked, +// its timestamp/size is checked against the cached timestamp/size. +// If a difference is found this task is taken off the queue, and an OpenTask +// is added to the mboxAccount queue in order to update our mailbox index. OpenTask +// receives a callback object that places this task back onto the mboxAccount queue. + +LIBMAIL_START + +class file; + +class mbox::LockTask : public mbox::TimedTask { + + class reopenCallback : public mail::callback { + LockTask *task; + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) + {} // TODO + + public: + reopenCallback(LockTask *task); + ~reopenCallback(); + void success(std::string); + void fail(std::string); + }; + + reopenCallback reopen; + + std::string filename; + // Filename to lock. Empty string: lock current folder + +public: + LockTask(mbox &mboxAccount, // Account + mail::callback &callbackArg, + std::string filenameArg=""); + // Empty string - current folder, non-empty -- some other file + + ~LockTask(); + + virtual bool locked(mbox::lock &mlock, std::string path); + // Mailbox file is now open. Create a file, then invoke + // the following: + + virtual bool locked(file &)=0; + bool doit(); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/mboxmagictag.C b/libmail/mboxmagictag.C new file mode 100644 index 0000000..16b9920 --- /dev/null +++ b/libmail/mboxmagictag.C @@ -0,0 +1,185 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "misc.H" +#include "mboxmagictag.H" + +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#include <sstream> + +using namespace std; + +static const char magicTag[]="X-MailPP-UID: "; + +mail::mboxMagicTag::mboxMagicTag(string header, + mail::keywords::Hashtable &h) + : tag("") +{ + if (strncmp(header.c_str(), magicTag, sizeof(magicTag)-1) == 0) + { + header=header.substr(sizeof(magicTag)-1); + + size_t colon=header.find(':'); + + if (colon != std::string::npos) + colon=header.find(':', colon+1); + + if (colon != std::string::npos) + { + // keywords + + string::iterator b=header.begin()+colon+1; + string::iterator e=header.end(); + + while (b != e) + { + if (*b == ' ') + { + ++b; + continue; + } + + string::iterator c=b; + + while (b != e) + { + if (*b == ' ') + break; + ++b; + } + keywords.addFlag(h, string(c, b)); + } + + header=header.substr(0, colon); + } + tag=header; + } +} + +mail::mboxMagicTag::mboxMagicTag(string uid, mail::messageInfo flags, + mail::keywords::Message keywordsArg) + : tag(""), keywords(keywordsArg) +{ + string t=""; + + if (!flags.unread) + t += "R"; + + if (!flags.recent) + t += "O"; + + if (flags.marked) + t += "F"; + + if (flags.replied) + t += "A"; + + if (flags.deleted) + t += "D"; + + if (flags.draft) + t += "d"; + + tag=t + ":" + uid; +} + +mail::mboxMagicTag::mboxMagicTag() +{ + string buffer; + + static size_t counter=0; + + { + ostringstream o; + + o << time(NULL) << '-' << getpid() << '-' << counter++; + buffer=o.str(); + } + + string h=buffer + '-' + mail::hostname(); + + size_t p; + + // Just in case, dump any colons from the uid. + + while ((p=h.find(':')) != std::string::npos) + h=h.substr(0, p) + h.substr(p+1); + + tag=":" + h; +} + +mail::mboxMagicTag::~mboxMagicTag() +{ +} + +mail::messageInfo mail::mboxMagicTag::getMessageInfo() const +{ + mail::messageInfo info; + + size_t n=tag.find(':'); + + if (n != std::string::npos) + info.uid=tag.substr(n+1); + + info.recent=true; + info.unread=true; + + const char *p=tag.c_str(); + + while (*p && *p != ':') + switch (*p++) { + case 'F': + info.marked=true; + break; + case 'A': + info.replied=true; + break; + case 'R': + info.unread=false; + break; + case 'O': + info.recent=false; + break; + case 'D': + info.deleted=true; + break; + case 'd': + info.draft=true; + break; + } + + return info; +} + +string mail::mboxMagicTag::toString() const +{ + set<string> kwSet; + set<string>::iterator b, e; + + keywords.getFlags(kwSet); + + string v=string(magicTag) + tag; + + const char *sep=":"; + + b=kwSet.begin(); + e=kwSet.end(); + + while (b != e) + { + v += sep; + v += *b; + sep=" "; + ++b; + } + + return v; +} diff --git a/libmail/mboxmagictag.H b/libmail/mboxmagictag.H new file mode 100644 index 0000000..f69ef8e --- /dev/null +++ b/libmail/mboxmagictag.H @@ -0,0 +1,57 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_mboxmagictag_h +#define libmail_mboxmagictag_h + +#include "libmail_config.h" +#include "mail.H" +#include "maildir/maildirkeywords.h" + +#include <string> + +LIBMAIL_START + +/////////////////////////////////////////////////////////////////////////// +// +// Each message in the mboxAccount file has a magic header prepended to it, as +// the first line after the From_ line. This header line is not seen by +// the application. The header line contains the message's UID, and flags. + +class mboxMagicTag { + + std::string tag; + + mail::keywords::Message keywords; + +public: + mboxMagicTag(std::string header, mail::keywords::Hashtable &h); + // Initialize from the first header line. + + mboxMagicTag(std::string uid, mail::messageInfo flags, + mail::keywords::Message keywordsArg); + // Manual initialization + + mboxMagicTag(); + // Create a new UID + + ~mboxMagicTag(); + + bool good() const { return tag.size() > 0; } + + mail::messageInfo getMessageInfo() const; + + std::string toString() const; + // Convert to a header line, sans the newline. + + + mail::keywords::Message &getKeywords() { return keywords; } + void setKeywords(const mail::keywords::Message &m) { keywords=m; } + +}; + +LIBMAIL_END + +#endif diff --git a/libmail/mboxmultilock.C b/libmail/mboxmultilock.C new file mode 100644 index 0000000..65394fa --- /dev/null +++ b/libmail/mboxmultilock.C @@ -0,0 +1,302 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "mboxmultilock.H" + +using namespace std; + +mail::mbox::MultiLock::MultiLock(mail::mbox &mboxAccount, + mail::callback &callbackArg) + :LockTask(mboxAccount, callbackArg) +{ +} + +mail::mbox::MultiLock::~MultiLock() +{ +} + +bool mail::mbox::MultiLock::locked(mail::mbox::lock &mlock, + string path) +{ + if (mboxAccount.multiLockLock && + mboxAccount.multiLockFilename == path) // This is me + ; + else + { + if (mboxAccount.multiLockLock) + { + delete mboxAccount.multiLockLock; + mboxAccount.multiLockLock=NULL; + } + + mboxAccount.multiLockFilename=path; + mboxAccount.multiLockLock=mlock.copy(); + } + + callback.success("Folder locked"); + done(); + return true; +} + +bool mail::mbox::MultiLock::locked(file &fileArg) // No-op +{ + return true; +} + +///////////////////////////////////////////////////////////////////////////// +// +// After obtaining a MultiLock, finally do genericMessageAttributes/Read + +mail::mbox::MultiLockGenericAttributes +::MultiLockGenericAttributes(mbox &mboxAccountArg, + const std::vector<size_t> &messagesArg, + MessageAttributes attributesArg, + mail::callback::message &callbackArg) + : mboxAccount(&mboxAccountArg), + messages(messagesArg), + attributes(attributesArg), + callback(callbackArg) +{ +} + +mail::mbox::MultiLockGenericAttributes::~MultiLockGenericAttributes() +{ +} + +void mail::mbox::MultiLockGenericAttributes::success(std::string message) +{ + try { + if (mboxAccount.isDestroyed()) + { + fail("Operation aborted."); + return; + } + + mboxAccount->genericAttributes(&*mboxAccount, + &*mboxAccount, + messages, + attributes, + callback); + delete this; + } catch (...) { + fail("An exception occured in mbox::MultiLockGenericAttributes"); + } +} + +void mail::mbox::MultiLockGenericAttributes::fail(string message) +{ + try { + callback.fail(message); + delete this; + } catch (...) { + delete this; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +void mail::mbox::MultiLockGenericAttributes +::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + callback.reportProgress(bytesCompleted, bytesEstimatedTotal, + messagesCompleted, messagesEstimatedTotal); +} + +// + +mail::mbox::MultiLockGenericMessageRead +::MultiLockGenericMessageRead(mbox &mboxAccountArg, + const vector<size_t> &messagesArg, + bool peekArg, + enum mail::readMode readTypeArg, + mail::callback::message &callbackArg) + : mboxAccount(&mboxAccountArg), + messages(messagesArg), + peek(peekArg), + readType(readTypeArg), + callback(callbackArg) +{ +} + +mail::mbox::MultiLockGenericMessageRead::~MultiLockGenericMessageRead() +{ +} + +void mail::mbox::MultiLockGenericMessageRead::success(std::string message) +{ + try { + if (mboxAccount.isDestroyed()) + { + fail("Operation aborted."); + return; + } + + mboxAccount->genericReadMessageContent(&*mboxAccount, + &*mboxAccount, + messages, + peek, + readType, + callback); + delete this; + } catch (...) { + fail("An exception occured in mbox::MultiLockGenericAttributes"); + } +} + +void mail::mbox::MultiLockGenericMessageRead::fail(std::string message) +{ + try { + callback.fail(message); + delete this; + } catch (...) { + delete this; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +void mail::mbox::MultiLockGenericMessageRead +::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + callback.reportProgress(bytesCompleted, bytesEstimatedTotal, + messagesCompleted, messagesEstimatedTotal); +} + +///////////////////////////////////////////////////////////////////////////// +// +// Cleanup. + +mail::mbox::MultiLockRelease::MultiLockRelease(mbox &mboxAccountArg, + callback::message + &origCallbackArg) + : origCallback(&origCallbackArg), + mboxAccount(&mboxAccountArg) +{ +} + +mail::mbox::MultiLockRelease::~MultiLockRelease() +{ + if (origCallback) + { + callback *p=origCallback; + + origCallback=NULL; + + try { + p->fail("Request aborted."); + delete p; + } catch (...) { + delete p; + } + } +} + +void mail::mbox::MultiLockRelease::success(std::string message) +{ + if (!mboxAccount.isDestroyed() && + mboxAccount->multiLockLock) + { + delete mboxAccount->multiLockLock; + mboxAccount->multiLockLock=NULL; + } + + callback *p=origCallback; + + origCallback=NULL; + + try { + p->success(message); + } catch (...) { + delete this; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + delete this; +} + +void mail::mbox::MultiLockRelease::fail(std::string message) +{ + if (!mboxAccount.isDestroyed() && + mboxAccount->multiLockLock) + { + delete mboxAccount->multiLockLock; + mboxAccount->multiLockLock=NULL; + } + + callback *p=origCallback; + + origCallback=NULL; + + try { + if (p) + p->fail(message); + delete this; + } catch (...) { + delete this; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +void mail::mbox::MultiLockRelease::messageEnvelopeCallback(size_t + messageNumber, + const envelope + &envelopeArg) +{ + if (origCallback) + origCallback->messageEnvelopeCallback(messageNumber, + envelopeArg); +} + +void mail::mbox::MultiLockRelease::messageArrivalDateCallback(size_t + messageNumber, + time_t datetime) +{ + if (origCallback) + origCallback->messageArrivalDateCallback(messageNumber, + datetime); +} + +void mail::mbox::MultiLockRelease::messageSizeCallback(size_t messageNumber, + unsigned long size) +{ + if (origCallback) + origCallback->messageSizeCallback(messageNumber, + size); +} + +void mail::mbox::MultiLockRelease::messageStructureCallback(size_t + messageNumber, + const mimestruct & + messageStructure) +{ + if (origCallback) + origCallback->messageStructureCallback(messageNumber, + messageStructure); +} + +void mail::mbox::MultiLockRelease::messageTextCallback(size_t n, + string text) +{ + if (origCallback) + origCallback->messageTextCallback(n, text); +} + +void mail::mbox::MultiLockRelease::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + size_t messagesCompleted, + size_t messagesEstimatedTot) +{ + if (origCallback) + origCallback->reportProgress(bytesCompleted, + bytesEstimatedTotal, + messagesCompleted, + messagesEstimatedTot); +} diff --git a/libmail/mboxmultilock.H b/libmail/mboxmultilock.H new file mode 100644 index 0000000..9100ff0 --- /dev/null +++ b/libmail/mboxmultilock.H @@ -0,0 +1,126 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_mboxmultilock_H +#define libmail_mboxmultilock_H + +#include "libmail_config.h" +#include "mboxlock.H" + +// +// A request for multiple messages would normally result in a separate lock +// attempt for each message. Horribly inefficient. +// +// What we do is open a lock first, and save it in the mail::mbox object. +// Before we actually try to lock something check if this lock is already +// obtained. If so, just reuse the lock +// + +LIBMAIL_START + +class mbox::MultiLock : public mbox::LockTask { + + bool locked(file &fileArg); // No-op + +public: + MultiLock(mbox &mboxAccount, + mail::callback &callbackArg); + ~MultiLock(); + + bool locked(mbox::lock &mlock, std::string path); +}; + +class mbox::MultiLockGenericAttributes : public callback { + + ptr<mbox> mboxAccount; + std::vector<size_t> messages; + MessageAttributes attributes; + mail::callback::message &callback; + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + +public: + MultiLockGenericAttributes(mbox &mboxAccountArg, + const std::vector<size_t> &messagesArg, + MessageAttributes attributesArg, + mail::callback::message &callbackArg); + ~MultiLockGenericAttributes(); + + void success(std::string message); + void fail(std::string message); +}; + +class mbox::MultiLockGenericMessageRead : public callback { + + ptr<mbox> mboxAccount; + const std::vector<size_t> messages; + bool peek; + + enum mail::readMode readType; + bool justHeader; + bool justContents; + callback::message &callback; + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + +public: + MultiLockGenericMessageRead(mbox &mboxAccountArg, + const std::vector<size_t> &messagesArg, + bool peekArg, + enum mail::readMode readTypeArg, + callback::message &callbackArg); + ~MultiLockGenericMessageRead(); + void success(std::string message); + void fail(std::string message); +}; + +// +// After we're done with a multilocked request, we can free the lock +// + +class mbox::MultiLockRelease : public callback::message { + + callback::message * volatile origCallback; + ptr<mbox> mboxAccount; +public: + MultiLockRelease(mbox &mboxAccountArg, + callback::message &origCallbackArg); + ~MultiLockRelease(); + + void success(std::string message); + void fail(std::string message); + + void messageEnvelopeCallback(size_t messageNumber, + const envelope &envelope); + + void messageArrivalDateCallback(size_t messageNumber, + time_t datetime); + + void messageSizeCallback(size_t messageNumber, + unsigned long size); + + void messageStructureCallback(size_t messageNumber, + const mimestruct &messageStructure); + + void messageTextCallback(size_t n, std::string text); + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + +}; + +LIBMAIL_END + +#endif diff --git a/libmail/mboxopen.C b/libmail/mboxopen.C new file mode 100644 index 0000000..54e108f --- /dev/null +++ b/libmail/mboxopen.C @@ -0,0 +1,282 @@ +/* +** Copyright 2002-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include <cstring> + +#include "mbox.H" +#include "mboxopen.H" +#include "mboxsighandler.H" +#include "mboxmagictag.H" +#include "file.H" +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <utime.h> + +using namespace std; + +mail::mbox::OpenTask::OpenTask(mail::mbox &mboxAccount, + string folderArg, mail::callback &callbackArg, + mail::callback::folder *openCallbackArg) + : TimedTask(mboxAccount, callbackArg), + folder(folderArg), + openCallback(openCallbackArg) +{ +} + +mail::mbox::OpenTask::~OpenTask() +{ +} + +bool mail::mbox::OpenTask::doit() +{ + bool isInbox= folder == "INBOX"; + + if (folder == "") + { + fail(strerror(ENOENT)); + return true; + } + + mail::mbox::lock mailbox_lock(isInbox ? mboxAccount.inboxMailboxPath:folder); + + mail::mbox::lock inbox_lock(mboxAccount.inboxSpoolPath); // Just in case + + struct stat stat_buf; + + if (isInbox) + { + // We must have a r/w lock on the mail spool file. + + if (!inbox_lock(false)) + { + if (errno == EAGAIN) + return false; // Deferral, try again later. + + // ENOENT is ok, the spool file does not exist. + + if (errno != ENOENT) + { + fail(strerror(errno)); + return true; + } + isInbox=false; + + // spool file not created yet, new account probably. + } + + // Make sure $HOME/mboxAccount exists. + int createMbox=::open(mboxAccount.inboxMailboxPath.c_str(), + O_RDWR|O_CREAT, 0600); + + if (createMbox < 0) + { + fail(strerror(errno)); + return true; + } + close(createMbox); + } + + bool hasExistingFolder= openCallback == NULL; + + // Opening an existing folder, in read-only mode. A read-only lock + // will be fine. + + bool ro=false; + + if (hasExistingFolder && mboxAccount.currentFolderReadOnly) + { + if (!mailbox_lock(true)) + { + if (errno != EAGAIN && errno != EEXIST) + { + fail(strerror(errno)); + return true; + } + return false; + } + } + + // Try for a read/write lock + + else if (!mailbox_lock(false)) + { + // If not permission denied, we're in trouble. + + if (errno != EPERM && errno != EACCES) + { + if (errno != EAGAIN && errno != EEXIST) + { + fail(strerror(errno)); + return true; + } + + // EAGAIN means try again + + return false; + + } + + if (isInbox) // Must have r/w to inbox + { + if (access(mboxAccount.inboxSpoolPath.c_str(), W_OK)) + { + fail("Invalid permissions on system mailbox."); + return true; + } + return false; + } + + // Try a read-only lock, then. + + if (!mailbox_lock(true)) + { + if (errno != EAGAIN) + { + fail(strerror(errno)); + return true; + } + return false; + } + ro=true; + } + + // Lock-n-load + + int mboxfd=mailbox_lock.getfd(); + + mail::file mboxFp(mboxfd, isInbox ? "r+":"r"); + + if (!mboxAccount.scan(mboxFp, NULL, openCallback == NULL, + NULL, + false, &callback)) + { + fail(errno == EINVAL + ? "File does not appear to be a mail folder." + : strerror(errno)); + return true; + } + + if (isInbox) + { + // Copy new mail. + + mail::mbox::sighandler updating(mboxfd); // Critical section + + mail::file spoolFp(inbox_lock.getfd(), "r"); + + if (!mboxAccount.scan(spoolFp, &mboxFp, openCallback == NULL, + NULL, + false, &callback)) + { + updating.rollback(); + + fail(errno == EINVAL + ? "File does not appear to be a mail folder." + : strerror(errno)); + return true; + } + + updating.block(); + if (ftruncate(inbox_lock.getfd(), 0) < 0) + ; // Ignore gcc warning + + struct utimbuf ut; + + if (fstat(mboxfd, &stat_buf) < 0) + { + fail(strerror(errno)); + return true; + } + + ut.modtime=--stat_buf.st_mtime; + ut.actime= ut.modtime; + + if (utime(mboxAccount.inboxMailboxPath.c_str(), &ut) < 0) + { + fail(strerror(errno)); + return true; + } + } + else if (fstat(mboxfd, &stat_buf) < 0) + { + fail(strerror(errno)); + return true; + } + + // Save folder particulars, to detect someone else's changes. + + mboxAccount.folderSavedTimestamp= stat_buf.st_mtime; + mboxAccount.folderSavedSize=stat_buf.st_size; + + const char *okmsg="OK"; + + if (openCallback) // We're doing mail::folder::open() + { + mboxAccount.currentFolderCallback=openCallback; + mboxAccount.currentFolderReadOnly=ro; + + mboxAccount.index.clear(); + + vector<mboxMessageIndex>::iterator b, e; + + b=mboxAccount.folderMessageIndex.begin(); + e=mboxAccount.folderMessageIndex.end(); + + while (b != e) + mboxAccount.index.push_back( (*b++).tag.getMessageInfo()); + + mboxAccount.currentFolder=folder; + + if (ro) + okmsg="WARNING: Folder opened in read-only mode."; + } + + opened(okmsg, callback); + return true; +} + +void mail::mbox::OpenTask::opened(const char *okmsg, mail::callback &callback) +{ + done(); + callback.success("Folder opened"); +} + +// +// Slight variation on the above - explicit new mail check. +// + + +mail::mbox::CheckNewMailTask::CheckNewMailTask(mail::mbox &mboxAccount, + string folderArg, + mail::callback &callbackArg, + mail::callback::folder + *openCallbackArg) + : OpenTask(mboxAccount, folderArg, callbackArg, openCallbackArg) +{ +} + +mail::mbox::CheckNewMailTask::~CheckNewMailTask() +{ +} + +void mail::mbox::CheckNewMailTask::opened(const char *okmsg, + mail::callback &callback) +{ + mboxAccount.checkNewMail(); + + if (mboxAccount.newMessages) + { + mboxAccount.newMessages=false; + if (mboxAccount.currentFolderCallback) + mboxAccount.currentFolderCallback->newMessages(); + } + OpenTask::opened(okmsg, callback); +} + + diff --git a/libmail/mboxopen.H b/libmail/mboxopen.H new file mode 100644 index 0000000..acceefa --- /dev/null +++ b/libmail/mboxopen.H @@ -0,0 +1,63 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_mboxopen_H +#define libmail_mboxopen_H + +#include "libmail_config.h" +#include "mbox.H" + +LIBMAIL_START + +class file; + +///////////////////////////////////////////////////////////////////////// +// +// A couple of tasks that open the mboxAccount folder. + + +class mbox::OpenTask : public mbox::TimedTask { + + std::string folder; + mail::callback::folder *openCallback; + + bool scan(file &scanFile, + file *saveFile, + bool skipDeleted); + +protected: + virtual void opened(const char *okmsg, mail::callback &callback); + +public: + OpenTask(mbox &mboxAccount, + std::string folderArg, mail::callback &callbackArg, + mail::callback::folder *openCallbackArg + // Not NULL - this is mail::account::Open(), and here's the new + // folder callback. + // NULL - opening the folder for some other reason + ); + + ~OpenTask(); + + bool doit(); +}; + +// +// Subclass OpenTask to implement a new mail check. + +class mbox::CheckNewMailTask : public mbox::OpenTask { + + void opened(const char *okmsg, mail::callback &callback); + +public: + CheckNewMailTask(mbox &mboxAccount, + std::string folderArg, mail::callback &callbackArg, + mail::callback::folder *openCallbackArg); + ~CheckNewMailTask(); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/mboxread.C b/libmail/mboxread.C new file mode 100644 index 0000000..71d46fc --- /dev/null +++ b/libmail/mboxread.C @@ -0,0 +1,165 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "mboxread.H" +#include "mboxmagictag.H" +#include "file.H" + +#include <ctype.h> +#include <errno.h> + +using namespace std; + +mail::mbox::GenericReadTask::GenericReadTask(mail::mbox &mboxAccount, + mail::callback::message + &callbackArg, + string uidArg, + size_t messageNumberArg, + bool peekArg, + mail::readMode + readTypeArg) + : LockTask(mboxAccount, callbackArg), + callback(callbackArg), + uid(uidArg), + messageNumber(messageNumberArg), + peek(peekArg), + readType(readTypeArg) +{ +} + +mail::mbox::GenericReadTask::~GenericReadTask() +{ +} + +bool mail::mbox::GenericReadTask::locked(mail::file &file) +{ + if (!mboxAccount.verifyUid(uid, messageNumber, callback)) + { + done(); + return true; + } + + mail::ptr<mail::mbox> me= &mboxAccount; + + off_t p=mboxAccount.folderMessageIndex[mboxAccount.uidmap.find(uid)->second] + .startingPos; + + if (fseek(file, p, SEEK_SET) < 0) + { + fail(strerror(errno)); + return true; + } + + bool firstLine=true; + bool inHeaders=true; + + string header=""; + + while (!feof(file) && !me.isDestroyed()) + { + string line=file.getline(); + + if (line.size() == 0 && feof(file)) + break; + + if (strncmp(line.c_str(), "From ", 5) == 0) + { + if (firstLine) + continue; + + break; + } + + if (firstLine) + { + firstLine=false; + + if (mail::mboxMagicTag(line, + mboxAccount.keywordHashtable) + .good()) + continue; // Ignore the magic tag line + } + + if (inHeaders) + { + if (line.size() == 0) + inHeaders=false; + + if (readType == mail::readHeadersFolded) + { + const char *p=line.c_str(); + + if (line.size() > 0 && + unicode_isspace((unsigned char)*p)) + { + while (unicode_isspace((unsigned char)*p)) + p++; + + header += " "; + header += p; + continue; + } + + if (header.size() > 0) + { + header += "\n"; + callback.messageTextCallback(messageNumber, + header); + } + header=line; + continue; + } + + line += "\n"; + callback.messageTextCallback(messageNumber, line); + continue; + } + + if (readType != mail::readBoth && + readType != mail::readContents) + { + header=""; + break; + } + + line += "\n"; + + const char *p=line.c_str(); + + if (*p == '>') + { + while (*p == '>') + p++; + + if (strncmp(p, "From ", 5) == 0) + line=line.substr(1); + // Dequote a >From_ line. + } + + callback.messageTextCallback(messageNumber, line); + } + + if (header.size() > 0) // Only headers, something got left over. + { + header += "\n"; + callback.messageTextCallback(messageNumber, header); + } + + if (!me.isDestroyed() && !peek && + mboxAccount.index[messageNumber].unread) + { + mboxAccount.index[messageNumber].unread=false; + mboxAccount.folderDirty=true; + if (mboxAccount.currentFolderCallback) + mboxAccount.currentFolderCallback + ->messageChanged(messageNumber); + } + + + callback.success("Folder locked."); + done(); + return true; +} diff --git a/libmail/mboxread.H b/libmail/mboxread.H new file mode 100644 index 0000000..3f4313c --- /dev/null +++ b/libmail/mboxread.H @@ -0,0 +1,43 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_mboxread_H +#define libmail_mboxread_H + +#include "libmail_config.h" +#include "mbox.H" +#include "mboxlock.H" + +LIBMAIL_START + +class file; + +/////////////////////////////////////////////////////////////////////////// +// +// Read a message, for generic::genericMessageRead() + +class mbox::GenericReadTask : public mbox::LockTask { + + mail::callback::message &callback; + std::string uid; + size_t messageNumber; + bool peek; + mail::readMode readType; + +public: + GenericReadTask(mbox &mboxAccount, + mail::callback::message &callbackArg, + std::string uidArg, + size_t messageNumberArg, + bool peekArg, + mail::readMode readTypeArg); + ~GenericReadTask(); + + bool locked(file &file); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/mboxsighandler.C b/libmail/mboxsighandler.C new file mode 100644 index 0000000..479b9eb --- /dev/null +++ b/libmail/mboxsighandler.C @@ -0,0 +1,80 @@ +/* +** Copyright 2002-2006, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" + +#include "mboxsighandler.H" + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <signal.h> +#include <string.h> + +mail::mbox::sighandler *mail::mbox::sighandler::curHandler; + +void mail::mbox::sighandler::handler(int sig) +{ + if (curHandler->fd >= 0) + if (ftruncate(curHandler->fd, curHandler->origSize) < 0) + ; // Ignore gcc warning + _exit(1); +} + +mail::mbox::sighandler::sighandler(int fdArg) + : fd(fdArg) +{ + struct stat stat_buf; + + if (fstat(fd, &stat_buf) < 0) + { + fd= -1; + return; + } + + origSize=stat_buf.st_size; + + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + + curHandler=this; + sa.sa_handler= &handler; + sigaction(SIGHUP, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); +} + +void mail::mbox::sighandler::block() +{ + sigset_t ss; + + sigemptyset(&ss); + sigaddset(&ss, SIGHUP); + sigaddset(&ss, SIGTERM); + sigaddset(&ss, SIGINT); + sigprocmask(SIG_BLOCK, &ss, NULL); +} + +mail::mbox::sighandler::~sighandler() +{ + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + + sa.sa_handler= SIG_DFL; + sigaction(SIGHUP, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + sigset_t ss; + + sigemptyset(&ss); + sigaddset(&ss, SIGHUP); + sigaddset(&ss, SIGTERM); + sigaddset(&ss, SIGINT); + sigprocmask(SIG_UNBLOCK, &ss, NULL); +} diff --git a/libmail/mboxsighandler.H b/libmail/mboxsighandler.H new file mode 100644 index 0000000..587c23b --- /dev/null +++ b/libmail/mboxsighandler.H @@ -0,0 +1,47 @@ +/* +** Copyright 2002-2006, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_mboxsighandler_H +#define libmail_mboxsighandler_H + +#include "libmail_config.h" +#include "mbox.H" + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +LIBMAIL_START + +/////////////////////////////////////////////////////////////////////// +// +// Trap process signals when updating an mboxAccount file. A received signal +// restores an mboxAccount file to its original size. + +class mbox::sighandler { + + int fd; + off_t origSize; + + static void handler(int); + + static mbox::sighandler *curHandler; + +public: + sighandler(int fd); + off_t getOrigSize() const { return (origSize); } + void block(); + void rollback() const + { + if (ftruncate(fd, origSize) < 0) + ; // Ignore gcc warning + } + + ~sighandler(); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/mimetypes.C b/libmail/mimetypes.C new file mode 100644 index 0000000..77800d1 --- /dev/null +++ b/libmail/mimetypes.C @@ -0,0 +1,86 @@ +/* +** Copyright 2002-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "mimetypes.H" +#include "namespace.H" +#include "unicode/unicode.h" +#include <fstream> +#include <cctype> +#include <cstring> + +using namespace std; + +LIBMAIL_START + +#include "mimetypefiles.h" + +LIBMAIL_END + +mail::mimetypes::mimetypes(string searchKey) +{ + size_t i; + + for (i=0; mimetypefiles[i]; i++) + { + ifstream f( mimetypefiles[i]); + + while (!f.bad() && !f.eof()) + { + string line; + + getline(f, line); + + size_t p; + + p=line.find('#'); + + if (p != std::string::npos) + line=line.substr(0, p); + + string::iterator b, e, c; + + vector<string> words; + + b=line.begin(); + e=line.end(); + + bool found=false; + + while (b != e) + { + if (unicode_isspace((unsigned char)*b)) + { + b++; + continue; + } + c=b; + while (b != e && !unicode_isspace((unsigned char) + *b)) + b++; + + string w=string(c, b); + + if (strcasecmp(w.c_str(), searchKey.c_str()) + == 0) + found=true; + words.push_back(w); + } + + if (found) + { + type=words[0]; + extensions.insert(extensions.end(), + words.begin()+1, + words.end()); + return; + } + } + } +} + +mail::mimetypes::~mimetypes() +{ +} diff --git a/libmail/mimetypes.H b/libmail/mimetypes.H new file mode 100644 index 0000000..476df82 --- /dev/null +++ b/libmail/mimetypes.H @@ -0,0 +1,42 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_mimetypes_H +#define libmail_mimetypes_H + +/////////////////////////////////////////////////////////////////////////// +// +// Convenient class for searching the mime.types file +// +// A mime.types file contains lines formatted as follows: +// +// type/subtype ext1 ext2 ext3 +// +// Filenames ending in .ext1, .ext2, or .ext3 are assigned to type/subtype +// MIME type + +#include <string> +#include <vector> + +#include "namespace.H" + +LIBMAIL_START + +class mimetypes { +public: + mimetypes(std::string searchKey); + // Either an extension or type/subtype + + ~mimetypes(); + + std::string type; + std::vector<std::string> extensions; + + bool found() { return type.size() > 0; } +}; + +LIBMAIL_END + +#endif diff --git a/libmail/misc.H b/libmail/misc.H new file mode 100644 index 0000000..2f108ba --- /dev/null +++ b/libmail/misc.H @@ -0,0 +1,54 @@ +/* +** Copyright 2003-2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_misc_H +#define libmail_misc_H + +#include "libmail_config.h" +#include "namespace.H" + +#include <string> + +LIBMAIL_START + +class loginInfo; + +std::string toutf8(std::string); +std::string fromutf8(std::string); + +std::string hostname(); + // Return this machine's name + +enum readMode {readHeadersFolded, readContents, readBoth, readHeaders}; + +void upper(std::string &w); + // + // Convert the string to uppercase characters using the ISO-8859-1 + // character set + +std::string homedir(); + // + // The home directory of the userid running this application. + +std::string loginUrlEncode(std::string method, + std::string server, std::string uid, + std::string password); + // Create a new mail account URL. + // method - the support method (imap, pop3Account, etc...) + // server - the name of the mail account's server + // uid - mail account ID + // pwd - mail account password + +bool loginUrlDecode(std::string url, class loginInfo &info); + // Decode a mail account URL + // + // url - the URL. + // + // info - login information + + +LIBMAIL_END + +#endif diff --git a/libmail/namespace.H b/libmail/namespace.H new file mode 100644 index 0000000..5fc7f33 --- /dev/null +++ b/libmail/namespace.H @@ -0,0 +1,13 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_namespace_H +#define libmail_namespace_H + +#define LIBMAIL_START namespace mail { + +#define LIBMAIL_END } + +#endif diff --git a/libmail/nntp.C b/libmail/nntp.C new file mode 100644 index 0000000..b7a51c5 --- /dev/null +++ b/libmail/nntp.C @@ -0,0 +1,1489 @@ +/* +** Copyright 2003-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "mail.H" +#include "misc.H" +#include "driver.H" +#include "nntp.H" +#include "nntpnewsrc.H" +#include "nntpxpat.H" +#include "copymessage.H" +#include "search.H" +#include "smtpinfo.H" +#include "objectmonitor.H" +#include "libcouriertls.h" +#include "expungelist.H" +#include <unicode/unicode.h> +#include <sstream> +#include <iostream> +#include <iomanip> +#include <time.h> +#include <errno.h> +#include <unistd.h> +#include <algorithm> +#include <fstream> +#include <vector> +#include <signal.h> +#include <set> +#include "nntplogin2.H" +#include "nntpfolder.H" +#include "nntplogout.H" +#include "nntpfetch.H" +#include "nntpcache.H" +#include "nntpxover.H" +#include "nntpchecknew.H" +#include "rfc2045/rfc2045.h" + +#define TIMEOUTINACTIVITY 15 + +using namespace std; + +mail::nntp::nntpMessageInfo::nntpMessageInfo() + : msgNum(0), msgFlag(0) +{ +} + +mail::nntp::nntpMessageInfo::~nntpMessageInfo() +{ +} + +///////////////////////////////////////////////////////////////////////////// + +LIBMAIL_START + +static bool open_nntp(mail::account *&accountRet, + mail::account::openInfo &oi, + mail::callback &callback, + mail::callback::disconnect &disconnectCallback) +{ + mail::loginInfo nntpLoginInfo; + + if (!mail::loginUrlDecode(oi.url, nntpLoginInfo)) + return false; + + if (nntpLoginInfo.method != "nntp" && + nntpLoginInfo.method != "nntps") + return false; + + accountRet=new mail::nntp(oi.url, oi.pwd, oi.certificates, + oi.extraString, + oi.loginCallbackObj, + callback, + disconnectCallback); + return true; +} + +static bool nntp_remote(string url, bool &flag) +{ + mail::loginInfo nntpLoginInfo; + + if (!mail::loginUrlDecode(url, nntpLoginInfo)) + return false; + + if (nntpLoginInfo.method != "nntp" && + nntpLoginInfo.method != "nntps") + return false; + + flag=true; + return true; +} + +driver nntp_driver= { &open_nntp, &nntp_remote }; + +LIBMAIL_END + +///////////////////////////////////////////////////////////////////////////// +// +// Service this account. +// + +void mail::nntp::handler(vector<pollfd> &fds, int &ioTimeout) +{ + int fd_save=getfd(); + + // Disconnect from the server, after a period of inactivity. + + if (inactivityTimeout) + { + time_t now=time(NULL); + + if (now + autologoutSetting < inactivityTimeout) + // Time reset?? + { + inactivityTimeout = now + autologoutSetting; + } + + if (now >= inactivityTimeout) + { + installTask(new mail::nntp::LogoutTask(NULL, + *this, + true)); + } + else + { + now = inactivityTimeout - now; + + if (now * 1000 <= ioTimeout) + { + ioTimeout = now * 1000; + } + } + } + + if (!tasks.empty()) + { + int t=(*tasks.begin())->getTimeout(); + + if (t * 1000 <= ioTimeout) + { + ioTimeout=t; + } + } + + MONITOR(mail::nntp); + + process(fds, ioTimeout); + + if (DESTROYED()) + ioTimeout=0; + + if (DESTROYED() || getfd() < 0) + { + size_t i; + + for (i=fds.size(); i; ) + { + --i; + + if (fds[i].fd == fd_save) + { + fds.erase(fds.begin()+i, fds.begin()+i+1); + break; + } + } + } + + return; +} + +void mail::nntp::resumed() +{ + if (!tasks.empty()) + (*tasks.begin())->resetTimeout(); +} + +// +// There's something to read. NNTP tasks read one line at a time. + +int mail::nntp::socketRead(const std::string &readbuffer) +{ + size_t n; + + if (!tasks.empty() && (n=readbuffer.find('\n')) != std::string::npos) + { + size_t i=n; + + if (i > 0 && readbuffer[i-1] == '\r') + --i; + + string line=readbuffer.substr(0, i); + + Task *t= *tasks.begin(); + + t->resetTimeout(); // Command is alive + t->serverResponse(line.c_str()); + return n+1; + } + + return 0; +} + +// +// Let the first task handle disconnection events. +// + +void mail::nntp::disconnect(const char *reason) +{ + string errmsg=reason ? reason:""; + + if (getfd() >= 0) + { + string errmsg2=socketDisconnect(); + + if (errmsg2.size() > 0) + errmsg=errmsg2; + } + + if (!tasks.empty()) + (*tasks.begin())->disconnected(errmsg.c_str()); +} + +mail::nntp::nntp(string url, string passwd, + std::vector<std::string> &certificates, + string newsrcFilenameArg, + mail::loginCallback *loginCallbackFunc, + callback &callback, + callback::disconnect &disconnectCallbackArg) + : mail::fd(disconnectCallbackArg, certificates), + inactivityTimeout(0), + folderCallback(NULL), + hasNewgroups(false), didCacheNewsrc(false), + disconnectCallback(&disconnectCallbackArg), + genericTmpFp(NULL), genericTmpRfcp(NULL) +{ + if (newsrcFilenameArg.size() == 0) + { + callback.fail("Invalid NNTP login (.newsrc unspecified)"); + return; + } + + newsrcFilename=newsrcFilenameArg; + + if (!mail::loginUrlDecode(url, savedLoginInfo)) + { + callback.fail("Invalid NNTP URL."); + return; + } + timeoutSetting=getTimeoutSetting(savedLoginInfo, "timeout", 60, + 30, 600); + autologoutSetting=getTimeoutSetting(savedLoginInfo, "autologout", 300, + 5, 1800); + + savedLoginInfo.loginCallbackFunc=loginCallbackFunc; + savedLoginInfo.use_ssl=savedLoginInfo.method == "nntps"; + if (passwd.size() > 0) + savedLoginInfo.pwd=passwd; + + try { + installTask(new LoginTask(&callback, *this)); + } catch (...) { + callback.fail(strerror(errno)); + } +} + +void mail::nntp::cleartmp() +{ + if (genericTmpFp != NULL) + { + fclose(genericTmpFp); + genericTmpFp=NULL; + } + + if (genericTmpRfcp != NULL) + { + rfc2045_free(genericTmpRfcp); + genericTmpRfcp=NULL; + } +} + +mail::nntp::~nntp() +{ + cleartmp(); + while (!tasks.empty()) + (*tasks.begin())->disconnected("NNTP connection aborted."); + + if (disconnectCallback) + { + callback::disconnect *d=disconnectCallback; + + disconnectCallback=NULL; + + d->disconnected("Connection aborted."); + } + index.clear(); +} + +// Cache newsrc into a map + +void mail::nntp::cacheNewsrc() +{ + if (didCacheNewsrc) + return; + + cachedNewsrc.clear(); + ifstream i(newsrcFilename.c_str()); + + string line; + + while (i.is_open() && !getline(i, line).fail()) + { + newsrc parseLine(line); + + if (parseLine.newsgroupname.size() == 0) + continue; + + cachedNewsrc.insert(make_pair(parseLine.newsgroupname, + parseLine)); + } + didCacheNewsrc=true; +} + +// Save an updated newsrc line + +bool mail::nntp::updateOpenedNewsrc(newsrc &n) +{ + updateCachedNewsrc(); + discardCachedNewsrc(); + + bool updated=false; + + string newNewsrcFilename=newsrcFilename + ".tmp"; + + ofstream o(newNewsrcFilename.c_str()); + + if (o.is_open()) + { + ifstream i(newsrcFilename.c_str()); + + string line; + + while (i.is_open() && !getline(i, line).fail()) + { + newsrc parseLine(line); + + if (parseLine.newsgroupname == n.newsgroupname) + { + o << (string)n << endl; + updated=true; + } + else + o << line << endl; + } + + if (!updated) + o << (string)n << endl; + + o << flush; + o.close(); + + if (!o.fail() && !o.bad() && + rename(newNewsrcFilename.c_str(), + newsrcFilename.c_str()) == 0) + return true; + } + return false; +} + +// For speed, we sort the cachedNewsrc by ptrs + +class mail::nntp::cachedNewsrcSort { +public: + bool operator()(newsrc *a, newsrc *b) + { + return a->newsgroupname < b->newsgroupname; + } +}; + +// Save updated newsrc cache + +bool mail::nntp::updateCachedNewsrc() +{ + if (!didCacheNewsrc) + return true; + + vector<newsrc *> vec; + + vec.reserve(cachedNewsrc.size()); + + map<string, newsrc>::iterator b=cachedNewsrc.begin(), + e=cachedNewsrc.end(); + + while (b != e) + { + vec.push_back(&b->second); + b++; + } + + sort(vec.begin(), vec.end(), cachedNewsrcSort()); + + string newNewsrcFilename=newsrcFilename + ".tmp"; + + ofstream o(newNewsrcFilename.c_str()); + + if (o.is_open()) + { + // Copy any extra lines... + + ifstream i(newsrcFilename.c_str()); + + string line; + + while (i.is_open() && !getline(i, line).fail()) + { + if (line[0] == '#') + { + o << line << endl; + continue; + } + } + i.close(); + + vector<newsrc *>::iterator b=vec.begin(), e=vec.end(); + + while (b != e) + { + o << (string)**b << endl; + b++; + } + o << flush; + o.close(); + + if (!o.fail() && !o.bad() && + rename(newNewsrcFilename.c_str(), + newsrcFilename.c_str()) == 0) + return true; + } + return false; +} + +// Discard cached newsrc + +void mail::nntp::discardCachedNewsrc() +{ + cachedNewsrc.clear(); + didCacheNewsrc=false; +} + +// Create a newsrc line based on the currently open folder index + +void mail::nntp::createNewsrc(newsrc &n) +{ + n.msglist.clear(); + + vector<nntpMessageInfo>::iterator + b=index.begin(), e=index.end(); + + if (b != e) + { + bool first=true; + + msgnum_t lastNum=0; + + while (b != e) + { + if (first) + { + if (b->msgNum > 1) + n.msglist.push_back(make_pair(1, + b->msgNum-1) + ); + } + else if (lastNum+1 != b->msgNum) + n.msglist.push_back(make_pair(lastNum+1, + b->msgNum-1)); + first=false; + lastNum= b->msgNum; + b++; + } + } + else if (hiWatermark > 1) + { + n.msglist.push_back(make_pair(1, hiWatermark-1)); + } +} + +void mail::nntp::installTask(Task *taskp) +{ + if (taskp == NULL) + LIBMAIL_THROW((strerror(errno))); + + bool wasEmpty=tasks.empty(); + + tasks.insert(tasks.end(), taskp); + inactivityTimeout=0; // We're active now + + if (wasEmpty) + { + Task *p= *tasks.begin(); + + p->resetTimeout(); + p->installedTask(); + } +} + +void mail::nntp::logout(callback &callback) +{ + installTask(new mail::nntp::LogoutTask(&callback, *this, false)); +} + +void mail::nntp::checkNewMail(callback &callback) +{ + if (openedGroup.size() > 0) + installTask(new mail::nntp::CheckNewTask(&callback, *this, + openedGroup)); + else + callback.success("Ok"); +} + +bool mail::nntp::hasCapability(string capability) +{ + if (capability == LIBMAIL_CHEAPSTATUS) + return true; + + return false; +} + +string mail::nntp::getCapability(string capability) +{ + if (capability == LIBMAIL_SERVERTYPE) + return "nntp"; + + if (capability == LIBMAIL_SERVERDESCR) + return "Usenet server"; + + return ""; +} + +mail::folder *mail::nntp::folderFromString(string s) +{ + string words[3]; + + words[0]=""; + words[1]=""; + words[2]=""; + + int i=0; + + string::iterator b=s.begin(), e=s.end(); + + while (b != e) + { + if (*b == ':') + { + b++; + ++i; + continue; + } + + if (*b == '\\') + { + b++; + if (b == e) + break; + } + + if (i < 3) + words[i].append(&*b, 1); + b++; + } + + return new mail::nntp::folder(this, words[2], + strchr(words[0].c_str(), 'F') != NULL, + strchr(words[0].c_str(), 'D') != NULL); +} + +void mail::nntp::readTopLevelFolders(callback::folderList &callback1, + callback &callback2) +{ + mail::nntp::folder subscribed(this, FOLDER_SUBSCRIBED, + false, true); + mail::nntp::folder all(this, FOLDER_NEWSRC, + false, true); + mail::nntp::folder newnews(this, FOLDER_CHECKNEW, + false, true); + mail::nntp::folder refresh(this, FOLDER_REFRESH, + false, true); + + vector<const mail::folder *> v; + + v.push_back(&subscribed); + v.push_back(&all); + v.push_back(&newnews); + v.push_back(&refresh); + + callback1.success(v); + callback2.success("OK"); +} + +void mail::nntp::findFolder(string folder, + callback::folderList &callback1, + callback &callback2) +{ + if (folder.size() == 0 || + (folder[0] == '/' && folder.find('.') == std::string::npos)) + { + mail::nntp::folder dummy(this, folder, false, true); + + vector<const mail::folder *> vec; + + vec.push_back(&dummy); + + callback1.success(vec); + callback2.success("OK"); + return; + } + + if (folder.size() > sizeof(FOLDER_SUBSCRIBED) && + folder.substr(0, sizeof(FOLDER_SUBSCRIBED)) == + FOLDER_SUBSCRIBED ".") + { + mail::nntp::folder dummy(this, folder, true, false); + + vector<const mail::folder *> vec; + + vec.push_back(&dummy); + + callback1.success(vec); + callback2.success("OK"); + return; + } + + bool foundFolder=false; + bool foundSubFolders=false; + + { + ifstream i(newsrcFilename.c_str()); + + if (i.is_open()) + { + string line; + + while (i.is_open() && !getline(i, line).fail()) + { + newsrc parseLine(line); + + if (parseLine.newsgroupname.size() == 0) + continue; + + string s=FOLDER_NEWSRC "." + parseLine.newsgroupname; + + if (s == folder) + foundFolder=true; + + if (s.size() > folder.size() && + s.substr(0, folder.size()) == folder && + s[folder.size()] == '.') + foundSubFolders = true; + } + } + } + + if (!foundFolder) + foundSubFolders=true; + + mail::nntp::folder dummy(this, folder, foundFolder, + foundSubFolders); + + vector<const mail::folder *> vec; + + vec.push_back(&dummy); + + callback1.success(vec); + callback2.success("OK"); +} + +string mail::nntp::translatePath(string path) +{ + // Evil trick: "" gets as the fake top-level hierarchy, + // readTopLevelFolders. Otherwise, unless the path already starts with + // a ".", prepend the SUBSCRIBED trunc. + + if (path.size() > 0 && path[0] != '/') + return FOLDER_SUBSCRIBED "." + path; + + return path; +} + +mail::folder *mail::nntp::getSendFolder(const smtpInfo &info, + const mail::folder *folder, + std::string &errmsg) +{ + if (info.recipients.size() == 0 && + folder == NULL && + info.options.find("POST") != info.options.end()) + { + // Sanity check + + + mail::folder *f=new mail::nntp::folder(this, "", false, + true); + // Any of our folders will do the trick. + + if (!f) + errmsg=strerror(errno); + return f; + } + + return mail::account::getSendFolder(info, folder, errmsg); +} + +void mail::nntp::readMessageAttributes(const vector<size_t> &messages, + MessageAttributes attributes, + callback::message &callback) +{ + bool useXover; + + if (attributes & MESSAGESIZE) + useXover=true; + else if (attributes & MIMESTRUCTURE) + useXover=false; + else if (attributes & (ENVELOPE | ARRIVALDATE)) + useXover=true; + else + useXover=false; + + if (useXover) + { + installTask(new XoverTask(&callback, *this, + openedGroup, messages, attributes)); + return; + } + + genericAttributes(this, this, messages, attributes, callback); +} + +bool mail::nntp::fixGenericMessageNumber(std::string uid, size_t &msgNum) +{ + msgnum_t n; + + istringstream i(uid); + + i >> n; + + if (i.bad() || i.fail()) + { + return false; + } + + // Need to fix up the message # + + size_t cnt=getFolderIndexSize(); + + if (cnt <= msgNum) + { + msgNum=cnt; + if (msgNum == 0) + { + return false; + } + --msgNum; + } + + while (n > index[msgNum].msgNum) + if (++msgNum >= cnt) + { + return false; + } + + while (msgNum > 0 && n < index[msgNum].msgNum) + --msgNum; + + if (n != index[msgNum].msgNum) + { + return false; + } + + return true; +} + +void mail::nntp::readMessageContent(const vector<size_t> &messages, + bool peek, + enum mail::readMode readType, + callback::message &callback) +{ + genericReadMessageContent(this, this, messages, peek, + readType, callback); +} + +void mail::nntp::readMessageContent(size_t messageNum, + bool peek, + const mimestruct &msginfo, + enum mail::readMode readType, + callback::message &callback) +{ + genericReadMessageContent(this, this, messageNum, peek, msginfo, + readType, callback); +} + +void mail::nntp::readMessageContentDecoded(size_t messageNum, + bool peek, + const mimestruct &msginfo, + callback::message &callback) +{ + genericReadMessageContentDecoded(this, this, messageNum, peek, + msginfo, callback); +} + +size_t mail::nntp::getFolderIndexSize() +{ + return openedGroup.size() == 0 ? 0:index.size(); +} + +mail::messageInfo mail::nntp::getFolderIndexInfo(size_t msgNum) +{ + if (msgNum < getFolderIndexSize()) + { + messageInfo dummy; + + unsigned char n=index[msgNum].msgFlag; + + dummy.marked= !!(n & IDX_FLAGGED); + dummy.deleted=!!(n & IDX_DELETED); + + ostringstream o; + + o << index[msgNum].msgNum; + + dummy.uid=o.str(); + return dummy; + } + + return messageInfo(); +} + +void mail::nntp::saveFolderIndexInfo(size_t msgNum, + const messageInfo &msgInfo, + callback &callback) +{ + if (msgNum < getFolderIndexSize()) + { + unsigned char n=0; + + if (msgInfo.marked) + n |= IDX_FLAGGED; + + if (msgInfo.deleted) + n |= IDX_DELETED; + + index[msgNum].msgFlag=n; + if (openedGroup.size() > 0) + folderCallback->messageChanged(msgNum); + } + callback.success("Message status updated."); +} + +void mail::nntp::genericMarkRead(size_t messageNumber) +{ +} + +void mail::nntp::updateFolderIndexFlags(const vector<size_t> &messages, + bool doFlip, + bool enableDisable, + const messageInfo &flags, + callback &callback) +{ + vector<size_t>::const_iterator b, e; + + b=messages.begin(); + e=messages.end(); + + size_t n=getFolderIndexSize(); + + while (b != e) + { + size_t i= *b++; + + if (i < n) + { + messageInfo dummy; + + unsigned char n=index[i].msgFlag; + + dummy.marked= !!(n & IDX_FLAGGED); + dummy.deleted=!!(n & IDX_DELETED); + +#define DOFLAG(d, field, dummy2) \ + if (flags.field) \ + { \ + dummy.field=\ + doFlip ? !dummy.field\ + : enableDisable; \ + } + + LIBMAIL_MSGFLAGS; +#undef DOFLAG + n=0; + + if (dummy.marked) + n |= IDX_FLAGGED; + + if (dummy.deleted) + n |= IDX_DELETED; + index[i].msgFlag=n; + } + } + + b=messages.begin(); + e=messages.end(); + + MONITOR(mail::nntp); + + while (!DESTROYED() && b != e) + { + size_t i= *b++; + + if (i < n && folderCallback) + folderCallback->messageChanged(i); + } + + saveSnapshot(); + callback.success("OK"); +} + +void mail::nntp::getFolderKeywordInfo(size_t msgNum, + set<string> &keywords) +{ + keywords.clear(); + if (msgNum >= index.size()) + return; + index[msgNum].keywords.getFlags(keywords); +} + +bool mail::nntp::genericProcessKeyword(size_t msgNum, + updateKeywordHelper &helper) +{ + if (msgNum >= index.size()) + return true; + helper.doUpdateKeyword(index[msgNum].keywords, keywordHashtable); + return true; +} + + +void mail::nntp::updateKeywords(const std::vector<size_t> &messages, + const std::set<std::string> &keywords, + bool setOrChange, + // false: set, true: see changeTo + bool changeTo, + callback &cb) +{ + MONITOR(mail::nntp); + + genericUpdateKeywords(messages, keywords, setOrChange, changeTo, + folderCallback, this, cb); + + if (!DESTROYED()) + saveSnapshot(); +} + + +void mail::nntp::updateFolderIndexInfo(callback &callback) +{ + vector<size_t> deletedMsgs; + + size_t n=getFolderIndexSize(); + size_t i; + + for (i=0; i<n; i++) + if (index[i].msgFlag & IDX_DELETED) + deletedMsgs.push_back(i); + + removeMessages(deletedMsgs, callback); +} + +void mail::nntp::removeMessages(const vector<size_t> &messages, + callback &callback) +{ + if (openedGroup.size() == 0) + { + callback.success("Ok."); + return; + } + + set<size_t> msgNumSet; + + msgNumSet.insert(messages.begin(), messages.end()); + + list< pair<size_t, size_t> > removedList; + size_t n=getFolderIndexSize(); + + MONITOR(mail::nntp); + + while (!DESTROYED() && n) + { + --n; + if (msgNumSet.find(n) == msgNumSet.end()) + continue; + + index.erase(index.begin() + n); + + if (!removedList.empty() && + removedList.begin()->first == n+1) + { + removedList.begin()->first--; + continue; + } + + if (removedList.size() >= 100) + { + vector< pair<size_t, size_t> > vec; + + vec.insert(vec.end(), removedList.begin(), + removedList.end()); + removedList.clear(); + + if (folderCallback) + folderCallback->messagesRemoved(vec); + } + + removedList.push_front(make_pair(n, n)); + } + + if (!DESTROYED() && removedList.size() > 0) + { + vector< pair<size_t, size_t> > vec; + + vec.insert(vec.end(), removedList.begin(), + removedList.end()); + removedList.clear(); + + if (folderCallback) + folderCallback->messagesRemoved(vec); + } + + if (!DESTROYED()) + { + updateCachedNewsrc(); + discardCachedNewsrc(); + + newsrc newNewsrc; + + createNewsrc(newNewsrc); + newNewsrc.newsgroupname=openedGroup; + newNewsrc.subscribed=true; + updateOpenedNewsrc(newNewsrc); + + saveSnapshot(); + } + + callback.success("Group updated."); +} + +void mail::nntp::saveSnapshot() +{ + if (folderCallback) + { + ostringstream o; + + o << loWatermark << '-' << hiWatermark; + + folderCallback->saveSnapshot(o.str()); + } +} + +void mail::nntp::copyMessagesTo(const vector<size_t> &messages, + mail::folder *copyTo, + callback &callback) +{ + copyMessages::copy(this, messages, copyTo, callback); +} + +void mail::nntp::searchMessages(const searchParams &searchInfo, + searchCallback &callback) +{ + string hdr; + switch (searchInfo.criteria) { + case searchParams::from: + hdr="FROM"; + break; + case searchParams::to: + hdr="TO"; + break; + case searchParams::cc: + hdr="CC"; + break; + case searchParams::bcc: + hdr="BCC"; + break; + case searchParams::subject: + hdr="SUBJECT"; + break; + case searchParams::header: + hdr=searchInfo.param1; + break; + default: + break; + } + + if (hdr.size() > 0) + { + unicode_char *uc; + size_t ucsize; + libmail_u_convert_handle_t + h(libmail_u_convert_tou_init(searchInfo.charset.c_str(), + &uc, &ucsize, 0)); + + if (h) + { + libmail_u_convert(h, searchInfo.param2.c_str(), + searchInfo.param2.size()); + + if (libmail_u_convert_deinit(h, NULL)) + uc=NULL; + } + else + { + uc=NULL; + } + + if (uc) + { + size_t i; + + for (i=0; i<ucsize; i++) + if (uc[i] < 32 || uc[i] >= 127) + break; + + if (i < ucsize) + { + free(uc); + } + else + { + char *p; + size_t psize; + + h=libmail_u_convert_fromu_init("iso-8859-1", + &p, &psize, 1); + + if (h) + { + libmail_u_convert_uc(h, uc, ucsize); + if (libmail_u_convert_deinit(h, NULL)) + p=NULL; + } + else + p=NULL; + + free(uc); + + if (!p) + { + callback.fail(strerror(errno)); + return; + } + + try { + string s=p; + searchMessagesXpat(hdr, s, + searchInfo + .searchNot, + searchInfo.scope, + searchInfo.rangeLo, + searchInfo.rangeHi, + callback); + free(p); + } catch (...) { + free(p); + callback.fail(strerror(errno)); + } + return; + } + } + } + + switch (searchInfo.criteria) { + case searchParams::replied: + case searchParams::deleted: + case searchParams::draft: + case searchParams::unread: + break; + default: + callback.fail("Not implemented."); + return; + } + + searchMessages::search(callback, searchInfo, this); +} + +void mail::nntp::searchMessagesXpat(string hdr, string srch, + bool searchNot, + searchParams::Scope searchScope, + size_t rangeLo, + size_t rangeHi, + searchCallback &callback) +{ + + string::iterator b, e; + + b=hdr.begin(); + e=hdr.end(); + + while (b != e) + { + if (*b < ' ' || *b >= 127) + { + errno=EINVAL; + callback.fail(strerror(errno)); + } + ++b; + } + + b=srch.begin(); + e=srch.end(); + + while (b != e) + { + if (*b < ' ' || *b >= 127) + { + errno=EINVAL; + callback.fail(strerror(errno)); + } + ++b; + } + + XpatTaskCallback *cb=new XpatTaskCallback(&callback); + + if (!cb) + { + callback.fail(strerror(errno)); + return; + } + + try { + installTask(new XpatTask(cb, *this, + openedGroup, + hdr, srch, searchNot, + searchScope, rangeLo, rangeHi)); + } catch (...) { + delete cb; + throw; + } +} + +void mail::nntp::genericMessageRead(string uid, + size_t messageNumber, + bool peek, + mail::readMode getType, + callback::message &callback) +{ + if (openedGroup.size() == 0) + { + callback.fail("Invalid message number."); + return; + } + + if (fixGenericMessageNumber(uid, messageNumber) && + genericCachedUid(uid)) + { + // We can optimize by taking this path + + vector<size_t> vec; + + vec.push_back(messageNumber); + readMessageContent(vec, peek, getType, callback); + return; + } + + installTask(new FetchTask(&callback, *this, openedGroup, + messageNumber, uid, getType)); +} + +void mail::nntp::genericMessageSize(string uid, + size_t messageNumber, + callback::message &callback) +{ + if (!fixGenericMessageNumber(uid, messageNumber)) + { + callback.fail("Invalid message number."); + return; + } + + callback.messageSizeCallback(messageNumber, 0); + + callback.success("OK"); // TODO +} + +void mail::nntp::genericGetMessageFd(string uid, + size_t messageNumber, + bool peek, + int &fdRet, + callback &callback) +{ + if (openedGroup.size() == 0) + { + callback.fail("Invalid message number."); + return; + } + + if (genericTmpFp && uid == cachedUid) + { + fdRet=fileno(genericTmpFp); + callback.success("OK"); + return; + } + + installTask(new CacheTask(&callback, *this, openedGroup, + messageNumber, uid, + &fdRet, NULL)); +} + +bool mail::nntp::genericCachedUid(string uid) +{ + return genericTmpFp && uid == cachedUid; +} + +void mail::nntp::genericGetMessageStruct(string uid, + size_t messageNumber, + struct rfc2045 *&structRet, + callback &callback) +{ + if (openedGroup.size() == 0) + { + callback.fail("Invalid message number."); + return; + } + + if (genericTmpRfcp && uid == cachedUid) + { + structRet=genericTmpRfcp; + callback.success("OK"); + return; + } + + installTask(new CacheTask(&callback, *this, openedGroup, + messageNumber, uid, + NULL, &structRet)); +} + +///////////////////////////////////////////////////////////////////////////// +// +// The basic NNTP task + +mail::nntp::Task::Task(callback *callbackArg, + nntp &myserverArg) + : callbackPtr(callbackArg), myserver( &myserverArg), + defaultTimeout( time(NULL) + myserver->timeoutSetting) +{ +} + +mail::nntp::Task::~Task() +{ + done(); // Make sure that any callback gets invoked +} + +void mail::nntp::Task::resetTimeout() +{ + defaultTimeout=time(NULL) + myserver->timeoutSetting; +} + +void mail::nntp::Task::done() +{ + callback *cb=callbackPtr; + + callbackPtr=NULL; + + if (myserver != NULL) // Kilroy was here + { + if ((*myserver->tasks.begin()) != this) + { + cerr << "INTERNAL ERROR: mail::nntp::Task::done()" + << endl; + abort(); + } + + myserver->tasks.erase(myserver->tasks.begin()); + + try { + if (!myserver->tasks.empty()) + { + Task *p= *myserver->tasks.begin(); + + p->resetTimeout(); + p->installedTask(); + } + else + emptyQueue(); + + } catch (...) { + myserver=NULL; + delete this; + if (cb) + cb->fail("Operation aborted."); + throw; + } + + + myserver=NULL; + delete this; + } + + if (cb) + cb->fail("Operation aborted."); +} + +void mail::nntp::Task::emptyQueue() +{ + myserver->inactivityTimeout=time(NULL) + myserver->autologoutSetting; +} + +// Subclass succeeded + +void mail::nntp::Task::success(string message) +{ + callback *cb=callbackPtr; + + callbackPtr=NULL; + done(); + if (cb) + cb->success(message); +} + +// Subclass failed + +void mail::nntp::Task::fail(string message) +{ + callback *cb=callbackPtr; + + callbackPtr=NULL; + done(); + if (cb) + cb->fail(message); +} + +// Check for task timeout. + +int mail::nntp::Task::getTimeout() +{ + time_t now; + + time(&now); + + if (now < defaultTimeout) + return defaultTimeout - now; + + string errmsg=myserver->socketDisconnect(); + + if (errmsg.size() == 0) + errmsg="Server timed out."; + + disconnected(errmsg.c_str()); + return 0; +} + +// Server has disconnected. +// The default implementation takes this task off the queue, +// calls the next Task's disconnect method, then deletes +// itself. + +void mail::nntp::Task::disconnected(const char *reason) +{ + callback *cb=callbackPtr; + + callbackPtr=NULL; + + if ((*myserver->tasks.begin()) != this) + { + cerr << "INTERNAL ERROR: mail::nntp::Task::done()" << endl; + abort(); + } + + nntp *s=myserver; + + myserver=NULL; + + s->tasks.erase(s->tasks.begin()); + delete this; + + if (!s->tasks.empty()) + (*s->tasks.begin())->disconnected(reason); + + if (cb) + cb->fail(reason); +} + +void mail::nntp::Task::installedTask() +{ +} diff --git a/libmail/nntp.H b/libmail/nntp.H new file mode 100644 index 0000000..e644f52 --- /dev/null +++ b/libmail/nntp.H @@ -0,0 +1,336 @@ +/* +** Copyright 2003-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_nntp_H +#define libmail_nntp_H + +#include "libmail_config.h" +#include "mail.H" +#include <sys/types.h> + +#include "maildir/maildirkeywords.h" + +#include "logininfo.H" +#include "fd.H" +#include "generic.H" +#include "search.H" +#include <stdio.h> +#include <time.h> +#include <string> +#include <map> +#include <list> +#include <vector> + +/////////////////////////////////////////////////////////////////////////// +// +// An NNTP implementation +// + +LIBMAIL_START + +////////////////////////////////////////////////////////////////////////////// + +class nntp : public fd, public generic { + +public: + class newsrc; + typedef unsigned long msgnum_t; + +private: + + time_t timeoutSetting; + time_t autologoutSetting; + + loginInfo nntpLoginInfo; + + loginInfo savedLoginInfo; + + time_t inactivityTimeout; + + callback::folder *folderCallback; + + void resumed(); + void handler(std::vector<pollfd> &fds, int &timeout); + int socketRead(const std::string &readbuffer); + + void disconnect(const char *reason); + + // Superclass of NNTP tasks. Methods create subclasses of Task + // objects, and push them to the tasks queue. Replies from the + // NNTP server are routed to the foremost Task on the queue. + + class Task { + + protected: + callback *callbackPtr; // App callback. + nntp * volatile myserver; + // Marked volatile due to destructor monkey business + + public: + time_t defaultTimeout; + + virtual void done(); + // Task completed, start the next task on the queue, + // and delete this Task object. + + void resetTimeout(); + + virtual void success(std::string); + virtual void fail(std::string); + // success/fail for use by subclasses. Invoke the callback + // method, then done. + + Task(callback *callbackArg, + nntp &myserverArg); + virtual ~Task(); + + virtual int getTimeout(); + // How long before this task times out. + + virtual void emptyQueue(); + // After removing this task, the task queue is now empty + + virtual void serverResponse(const char *message)=0; + // Process a line of text from the server + + virtual void disconnected(const char *reason); + // Server has disconnected. + // The default implementation takes this task off the queue, + // calls the next Task's disconnect method, then deletes + // itself. + + virtual void installedTask(); + // This task is now at the front of the queue + }; + + std::list<Task *> tasks; + + std::vector<std::string> newgroups; + bool hasNewgroups; + + std::map<std::string, newsrc> cachedNewsrc; + bool didCacheNewsrc; + + void cacheNewsrc(); + bool updateCachedNewsrc(); + void discardCachedNewsrc(); + bool updateOpenedNewsrc(newsrc &); + + void createNewsrc(newsrc &); + + class cachedNewsrcSort; + + class folder; + class add; + class LoggedInTask; + class LoginTask; + class LogoutTask; + class ListActiveTask; + class GroupTask; + class GroupInfoTask; + class GroupOpenTask; + class FetchTask; + class FetchTaskBase; + class CacheMessageTask; + class CacheTask; + class XoverTask; + class PostTask; + class CheckNewTask; + + class XpatTask; + class XpatTaskCallback; + + std::string newsrcFilename; // .newsrc file + + std::string openedGroup; // Group we have logically opened + + // Here's the index of an opened group. 'cause Usenet groups can + // be fairly large, we try to be extra skimpy on memory usage. + + class nntpMessageInfo { + public: + nntpMessageInfo(); + ~nntpMessageInfo(); + msgnum_t msgNum; + + mail::keywords::Message keywords; + + unsigned char msgFlag; +#define IDX_DELETED 1 +#define IDX_FLAGGED 2 + +#define IDX_SEARCH 128 // Flag used to mark msgs found by search + }; + +private: + static bool equalMsgNums(nntpMessageInfo a, + nntpMessageInfo b); + +public: + mail::keywords::Hashtable keywordHashtable; + std::vector<nntpMessageInfo> index; + + msgnum_t loWatermark, hiWatermark; // Saved from last GROUP + + std::string serverGroup; // Group actually open on the server + + callback::disconnect *disconnectCallback; + + void installTask(Task *); +public: + + friend class Task; + friend class LoggedInTask; + friend class folder; + friend class LogoutTask; + friend class ListActiveTask; + friend class GroupTask; + friend class GroupInfoTask; + friend class GroupOpenTask; + friend class FetchTask; + friend class FetchTaskBase; + friend class CacheMessageTask; + friend class CacheTask; + friend class XoverTask; + friend class add; + friend class PostTask; + friend class CheckNewTask; + friend class XpatTask; + friend class XpatTaskCallback; + + nntp(std::string url, std::string passwd, + std::vector<std::string> &certificates, + std::string newsrcFilename, + mail::loginCallback *loginCallbackFunc, + callback &callback, + callback::disconnect &disconnectCallbackArg); + + nntp(const nntp &); // UNDEFINED + nntp &operator=(const nntp &); // UNDEFINED + + ~nntp(); + + void logout(callback &callback); + + void checkNewMail(callback &callback); + bool hasCapability(std::string capability); + std::string getCapability(std::string capability); + + mail::folder *folderFromString(std::string); + + void readTopLevelFolders(callback::folderList &callback1, + callback &callback2); + void findFolder(std::string folder, + class callback::folderList &callback1, + class callback &callback2); + std::string translatePath(std::string path); + + mail::folder *getSendFolder(const smtpInfo &info, + const mail::folder *folder, + std::string &errmsg); + + void readMessageAttributes(const std::vector<size_t> &messages, + MessageAttributes attributes, + callback::message &callback); + + void readMessageContent(const std::vector<size_t> &messages, + bool peek, + enum mail::readMode readType, + callback::message &callback); + + void readMessageContent(size_t messageNum, + bool peek, + const mimestruct &msginfo, + enum mail::readMode readType, + callback::message &callback); + + void readMessageContentDecoded(size_t messageNum, + bool peek, + const mimestruct &msginfo, + callback::message &callback); + + size_t getFolderIndexSize(); + messageInfo getFolderIndexInfo(size_t); + + void saveFolderIndexInfo(size_t, + const messageInfo &, + callback &); + + void updateFolderIndexFlags(const std::vector<size_t> &messages, + bool doFlip, + bool enableDisable, + const messageInfo &flags, + callback &callback); + + void updateFolderIndexInfo(callback &); + + void getFolderKeywordInfo(size_t, std::set<std::string> &); + + void updateKeywords(const std::vector<size_t> &messages, + const std::set<std::string> &keywords, + bool setOrChange, + // false: set, true: see changeTo + bool changeTo, + callback &cb); +private: + bool genericProcessKeyword(size_t msgNum, + updateKeywordHelper &helper); +public: + void removeMessages(const std::vector<size_t> &messages, + callback &cb); + + void copyMessagesTo(const std::vector<size_t> &messages, + mail::folder *copyTo, + callback &callback); + + void searchMessages(const searchParams &searchInfo, + searchCallback &callback); + + void saveSnapshot(); +private: + void searchMessagesXpat(std::string hdr, std::string srch, + bool searchNot, + searchParams::Scope searchScope, + size_t rangeLo, size_t rangeHi, + searchCallback &callback); + + bool fixGenericMessageNumber(std::string uid, size_t &messageNumber); + + void genericMessageRead(std::string uid, + size_t messageNumber, + bool peek, + mail::readMode readTypeArg, + callback::message &callback); + + void genericMessageSize(std::string uid, + size_t messageNumber, + callback::message &callback); + + void genericGetMessageFd(std::string uid, + size_t messageNumber, + bool peek, + int &fdRet, + callback &callback); + void genericMarkRead(size_t messageNumber); + + void genericGetMessageStruct(std::string uid, + size_t messageNumber, + struct rfc2045 *&structRet, + callback &callback); + + bool genericCachedUid(std::string uid); + + // One message is cached to a temp file, and parsed. + + std::string cachedUid; + FILE *genericTmpFp; + struct rfc2045 *genericTmpRfcp; + + void cleartmp(); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/nntpadd.C b/libmail/nntpadd.C new file mode 100644 index 0000000..7c055a1 --- /dev/null +++ b/libmail/nntpadd.C @@ -0,0 +1,70 @@ +/* +** Copyright 2003-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "nntpadd.H" +#include "nntppost.H" +#include <errno.h> +#include <cstring> + +using namespace std; + +mail::nntp::add::add(mail::nntp *myServerArg, mail::callback *callbackArg) + : addMessage(myServerArg), myServer(myServerArg), + origCallback(callbackArg), tfile(tmpfile()), byteCount(0) +{ +} + +mail::nntp::add::~add() +{ + if (tfile) + fclose(tfile); +} + +void mail::nntp::add::saveMessageContents(std::string s) +{ + byteCount += s.size(); + + if (tfile) + if (fwrite(s.c_str(), s.size(), 1, tfile) != 1) + ; // Ignore gcc warning +} + +void mail::nntp::add::go() +{ + if (!tfile || fflush(tfile) < 0 || ferror(tfile) < 0) + { + fail(strerror(errno)); + return; + } + + if (!checkServer()) + return; + + PostTask *p=new PostTask(origCallback, *myServer, tfile); + + if (!p) + { + fail(strerror(errno)); + return; + } + + try { + myServer->installTask(p); + tfile=NULL; + origCallback=NULL; + } catch (...) { + tfile=p->msg; + p->msg=NULL; + delete p; + throw; + } + delete this; +} + +void mail::nntp::add::fail(string errmsg) +{ + origCallback->fail(errmsg); + delete this; +} diff --git a/libmail/nntpadd.H b/libmail/nntpadd.H new file mode 100644 index 0000000..0eb3654 --- /dev/null +++ b/libmail/nntpadd.H @@ -0,0 +1,39 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_nntpadd_H +#define libmail_nntpadd_H + +#include "libmail_config.h" + +#include "nntp.H" +#include "addmessage.H" + +#include <stdio.h> + +LIBMAIL_START + +// Posting messages to an nntp server + +class mail::nntp::add : public addMessage { + + nntp *myServer; + mail::callback *origCallback; + + FILE *tfile; + + unsigned long byteCount; + +public: + add(mail::nntp *myServerArg, mail::callback *callbackArg); + ~add(); + + void saveMessageContents(std::string); + void go(); + void fail(std::string errmsg); +}; + +LIBMAIL_END +#endif diff --git a/libmail/nntpcache.C b/libmail/nntpcache.C new file mode 100644 index 0000000..f5b6bfc --- /dev/null +++ b/libmail/nntpcache.C @@ -0,0 +1,86 @@ +/* +** Copyright 2003-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "nntpcache.H" +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <cstring> + +mail::nntp::CacheTask::CacheTask(mail::callback *callbackArg, + nntp &myserverArg, + std::string groupNameArg, + size_t msgNumArg, + std::string uidArg, + + int *cacheFdArg, + struct rfc2045 **cacheStructArg) + : FetchTaskBase(callbackArg, myserverArg, groupNameArg, + msgNumArg, uidArg, mail::readBoth), + cacheFd(cacheFdArg), + cacheStruct(cacheStructArg), + tmpfile(NULL), + rfc2045p(NULL) +{ +} + +mail::nntp::CacheTask::~CacheTask() +{ + if (tmpfile) + fclose(tmpfile); + + if (rfc2045p) + rfc2045_free(rfc2045p); +} + +void mail::nntp::CacheTask::loggedIn() +{ + if ((tmpfile= ::tmpfile()) == NULL || + (rfc2045p=rfc2045_alloc()) == NULL) + { + fail(strerror(errno)); + return; + } + + FetchTaskBase::loggedIn(); +} + +void mail::nntp::CacheTask::fetchedText(std::string txt) +{ + if (fwrite(txt.c_str(), txt.size(), 1, tmpfile) != 1) + ; // Ignore gcc warning + rfc2045_parse(rfc2045p, txt.c_str(), txt.size()); +} + +void mail::nntp::CacheTask::success(std::string msg) +{ + rfc2045_parse_partial(rfc2045p); + + if (fflush(tmpfile) < 0 || ferror(tmpfile)) + { + fail(strerror(errno)); + return; + } + + fcntl(fileno(tmpfile), F_SETFD, FD_CLOEXEC); + + myserver->cleartmp(); + + myserver->cachedUid=uid; + myserver->genericTmpFp=tmpfile; + tmpfile=NULL; + + myserver->genericTmpRfcp=rfc2045p; + rfc2045p=NULL; + + if (cacheFd) + *cacheFd=fileno(myserver->genericTmpFp); + + if (cacheStruct) + *cacheStruct=myserver->genericTmpRfcp; + + FetchTaskBase::success(msg); +} diff --git a/libmail/nntpcache.H b/libmail/nntpcache.H new file mode 100644 index 0000000..cf1974b --- /dev/null +++ b/libmail/nntpcache.H @@ -0,0 +1,51 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_nntpcache_H +#define libmail_nntpcache_H + +#include "libmail_config.h" + +#include "nntp.H" +#include "nntpfetch.H" + +#include <string> + +#include "rfc2045/rfc2045.h" +#include <stdio.h> + +LIBMAIL_START + +// +// Cache message contents. +// + +class mail::nntp::CacheTask : public mail::nntp::FetchTaskBase { + + int *cacheFd; + struct rfc2045 **cacheStruct; + + FILE *tmpfile; + struct rfc2045 *rfc2045p; + +public: + CacheTask(mail::callback *callbackArg, nntp &myserverArg, + std::string groupNameArg, + size_t msgNumArg, + std::string uidArg, + + int *cacheFdArg, + struct rfc2045 **cacheStructArg); + + ~CacheTask(); + + void success(std::string msg); + void loggedIn(); + void fetchedText(std::string); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/nntpchecknew.C b/libmail/nntpchecknew.C new file mode 100644 index 0000000..0328f6c --- /dev/null +++ b/libmail/nntpchecknew.C @@ -0,0 +1,214 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ + +#include "nntpchecknew.H" +#include "nntpnewsrc.H" +#include <sstream> +#include <algorithm> + +using namespace std; + +mail::nntp::CheckNewTask::CheckNewTask(callback *callbackArg, + nntp &myserverArg, + std::string groupNameArg) + : LoggedInTask(callbackArg, myserverArg), + groupName(groupNameArg) +{ +} + +mail::nntp::CheckNewTask::~CheckNewTask() +{ +} + +void mail::nntp::CheckNewTask::loggedIn() +{ + myserver->serverGroup=""; + myserver->socketWrite("GROUP " + groupName + "\r\n"); + response_func= &mail::nntp::CheckNewTask::processGroupStatus; +} + +void mail::nntp::CheckNewTask::processLine(const char *message) +{ + (this->*response_func)(message); +} + +void mail::nntp::CheckNewTask::processGroupStatus(const char *msg) +{ + string buf=msg; + istringstream i(buf); + + int status; + msgnum_t est, lo, hi; + + i >> status >> est >> lo >> hi; + + if (i.bad() || i.fail() || (status / 100) != 2) + { + fail(msg); + return; + } + +#if 0 + // DEBUG + + if (myserver->idxMsgNums.size() > 0) + lo=myserver->idxMsgNums[0]+1; + + // DEBUG +#endif + + myserver->serverGroup=groupName; + + loWatermark= lo; + hiWatermark= ++hi; + + if (lo == hi || myserver->hiWatermark >= hiWatermark) + { + ptr<mail::nntp> s=myserver; + + checkPurged(); + + if (!s.isDestroyed()) + { + s->saveSnapshot(); + success("Ok"); + } + + return; + } + + ostringstream o; + + firstNewMsg=myserver->hiWatermark; + + if (firstNewMsg >= lo + (hi-lo)/2) + { + // Cheaper to use XHDR + + o << "XHDR Lines " << firstNewMsg << "-\r\n"; + } + else + { + o << "LISTGROUP\r\n"; + } + + response_func=&mail::nntp::CheckNewTask::processXhdrStatus; + + myserver->socketWrite(o.str()); +} + +void mail::nntp::CheckNewTask::processXhdrStatus(const char *msg) +{ + if (msg[0] == '4') + { + response_func= + &mail::nntp::CheckNewTask::processXhdrList; + + processXhdrList("."); + return; + } + + if (msg[0] != '2') + { + fail(msg); + return; + } + + response_func= &mail::nntp::CheckNewTask::processXhdrList; +} + +bool mail::nntp::equalMsgNums(nntpMessageInfo a, + nntpMessageInfo b) +{ + return a.msgNum == b.msgNum; +} + +void mail::nntp::CheckNewTask::processXhdrList(const char *msg) +{ + if (strcmp(msg, ".") == 0) + { + sort(newMsgList.begin(), newMsgList.end()); + + vector<msgnum_t>::iterator p= + unique(newMsgList.begin(), + newMsgList.end()); + + ptr<mail::nntp> s=myserver; + + checkPurged(); + + if (s.isDestroyed()) + return; + + myserver->hiWatermark=hiWatermark; + newMsgList.erase(p, newMsgList.end()); + + + vector<msgnum_t>::iterator b=newMsgList.begin(), + e=newMsgList.end(); + + while (b != e) + { + mail::nntp::nntpMessageInfo mi; + + mi.msgNum= *b; + ++b; + myserver->index.push_back(mi); + } + + if (newMsgList.size() > 0 && myserver->folderCallback) + myserver->folderCallback->newMessages(); + + if (!s.isDestroyed()) + { + s->saveSnapshot(); + success("Ok"); + } + return; + } + + string s(msg); + istringstream i(s); + + msgnum_t n; + + i >> n; + + if (!i.bad() && !i.fail() && n >= firstNewMsg) + { + newMsgList.push_back(n); + } +} + +void mail::nntp::CheckNewTask::checkPurged() +{ + size_t n=myserver->index.size(); + size_t i; + + for (i=0; i<n; i++) + if (myserver->index[i].msgNum >= loWatermark) + break; + + myserver->loWatermark=loWatermark; + if (i > 0 && myserver->folderCallback) + { + myserver->index.erase(myserver->index.begin(), + myserver->index.begin()+i); + + vector< pair<size_t, size_t> > del; + + del.push_back(make_pair(0, i-1)); + + newsrc myNewsrc; + + myNewsrc.newsgroupname=groupName; + + myserver->createNewsrc(myNewsrc); + myserver->updateOpenedNewsrc(myNewsrc); + + myserver->folderCallback->messagesRemoved(del); + } +} diff --git a/libmail/nntpchecknew.H b/libmail/nntpchecknew.H new file mode 100644 index 0000000..64d5032 --- /dev/null +++ b/libmail/nntpchecknew.H @@ -0,0 +1,54 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_nntpchecknew_H +#define libmail_nntpchecknew_H + +#include "libmail_config.h" + +#include "nntp.H" +#include "nntplogin.H" + +#include <string> +#include <vector> + +LIBMAIL_START + +// +// Check for new messages in a group + +class mail::nntp::CheckNewTask : public mail::nntp::LoggedInTask { + + void (mail::nntp::CheckNewTask::*response_func)(const char *); + + std::string groupName; + + std::vector<msgnum_t> newMsgList; + + msgnum_t loWatermark, hiWatermark; + + msgnum_t firstNewMsg; + +public: + + CheckNewTask(callback *callbackArg, nntp &myserverArg, + std::string groupNameArg); + ~CheckNewTask(); + + void loggedIn(); + void processLine(const char *message); + +private: + void processGroupStatus(const char *); + void processXhdrStatus(const char *); + void processXhdrList(const char *); + + void checkPurged(); + +}; + +LIBMAIL_END + +#endif diff --git a/libmail/nntpfetch.C b/libmail/nntpfetch.C new file mode 100644 index 0000000..4bd80a9 --- /dev/null +++ b/libmail/nntpfetch.C @@ -0,0 +1,186 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "nntpfetch.H" +#include <sstream> + +using namespace std; + +mail::nntp::FetchTaskBase::FetchTaskBase(mail::callback *callbackArg, + nntp &myserverArg, + std::string groupNameArg, + size_t msgNumArg, + std::string uidArg, + mail::readMode getTypeArg) + : GroupTask(callbackArg, myserverArg, groupNameArg), + msgNum(msgNumArg), + uid(uidArg), + getType(getTypeArg) +{ +} + +mail::nntp::FetchTaskBase::~FetchTaskBase() +{ +} + +void mail::nntp::FetchTaskBase::selectedGroup(msgnum_t estimatedCount, + msgnum_t loArticleCount, + msgnum_t hiArticleCount) +{ + response_func= &mail::nntp::FetchTaskBase::processStatusResponse; + + if (!myserver->fixGenericMessageNumber(uid, msgNum)) + { + fail("Invalid message number."); + return; + } + + byteCount=0; + ostringstream o; + + switch (getType) { + case mail::readContents: + o << "BODY "; + break; + case mail::readBoth: + o << "ARTICLE "; + break; + default: + o << "HEAD "; + } + + o << myserver->index[msgNum].msgNum << "\r\n"; + + myserver->socketWrite(o.str()); +} + +void mail::nntp::FetchTaskBase::processGroup(const char *cmd) +{ + (this->*response_func)(cmd); +} + +void mail::nntp::FetchTaskBase::processStatusResponse(const char *msg) +{ + if (msg[0] != '2') + { + ostringstream o; + + o << "The following error occured while reading this article:\n" + "\n" + " " << msg << "\n\n"; + + fetchedText(o.str()); + success("Ok"); + return; + } + + response_func= getType == mail::readHeadersFolded + ? &mail::nntp::FetchTaskBase::processFetchFoldedResponse: + &mail::nntp::FetchTaskBase::processFetchResponse; + foldedNewline=false; +} + +void mail::nntp::FetchTaskBase::processFetchResponse(const char *cmd) +{ + if (strcmp(cmd, ".")) + { + if (*cmd == '.') + ++cmd; + + string s(cmd); + + s += "\n"; + + byteCount += s.size(); + if (callbackPtr) + callbackPtr->reportProgress(byteCount, 0, 0, 1); + fetchedText(s); + return; + } + + if (callbackPtr) + callbackPtr->reportProgress(byteCount, byteCount, 0, 1); + success("Ok\n"); +} + +void mail::nntp::FetchTaskBase::processFetchFoldedResponse(const char *cmd) +{ + if (strcmp(cmd, ".")) + { + if (*cmd == '.') + ++cmd; + + if (*cmd && unicode_isspace((unsigned char)*cmd) && + foldedNewline) + { + while (*cmd && unicode_isspace((unsigned char)*cmd)) + cmd++; + + string s(" "); + s += cmd; + + byteCount += s.size(); + + if (callbackPtr) + callbackPtr-> + reportProgress(byteCount, 0, 0, 1); + fetchedText(s); + } + else + { + string s; + + if (foldedNewline) + s += "\n"; + foldedNewline=true; + s += cmd; + + byteCount += s.size(); + if (callbackPtr) + callbackPtr-> + reportProgress(byteCount, 0, 0, 1); + fetchedText(s); + } + return; + } + ++byteCount; + + if (callbackPtr) + callbackPtr->reportProgress(byteCount, byteCount, 0, 1); + + fetchedText("\n"); + success("Ok"); +} + +// +// TaskBase is recyled by CacheTask, which also fetches a message's contents, +// but does not have a callback::message, because it goes into a temporary +// file. That's why we separate out callback::message-specific processing +// into a subclass. +// + +mail::nntp::FetchTask::FetchTask(callback::message *textCallbackArg, + nntp &myserverArg, + std::string groupNameArg, + size_t msgNumArg, + std::string uidArg, + mail::readMode getType) + : FetchTaskBase(textCallbackArg, myserverArg, + groupNameArg, + msgNumArg, + uidArg, + getType), + textCallback(*textCallbackArg) +{ +} + +mail::nntp::FetchTask::~FetchTask() +{ +} + +void mail::nntp::FetchTask::fetchedText(std::string s) +{ + textCallback.messageTextCallback(msgNum, s); +} diff --git a/libmail/nntpfetch.H b/libmail/nntpfetch.H new file mode 100644 index 0000000..73a4f56 --- /dev/null +++ b/libmail/nntpfetch.H @@ -0,0 +1,74 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_nntpfetch_H +#define libmail_nntpfetch_H + +#include "libmail_config.h" + +#include "nntp.H" +#include "nntpgroup.H" + +#include <string> + +LIBMAIL_START + +// +// Fetch message contents. +// + +class mail::nntp::FetchTaskBase : public mail::nntp::GroupTask { + +protected: + size_t msgNum; + std::string uid; + mail::readMode getType; + + unsigned long byteCount; + + void (mail::nntp::FetchTaskBase::*response_func)(const char *); + +public: + FetchTaskBase(mail::callback *callbackArg, nntp &myserverArg, + std::string groupNameArg, + size_t msgNumArg, + std::string uidArg, + mail::readMode getTypeArg); + ~FetchTaskBase(); + + void selectedGroup(msgnum_t estimatedCount, + msgnum_t loArticleCount, + msgnum_t hiArticleCount); + void processGroup(const char *); + +private: + void processStatusResponse(const char *); + + void processFetchResponse(const char *); + + bool foldedNewline; + void processFetchFoldedResponse(const char *); + + virtual void fetchedText(std::string)=0; +}; + +class mail::nntp::FetchTask : public mail::nntp::FetchTaskBase { + + callback::message &textCallback; +public: + FetchTask(callback::message *textCallbackArg, nntp &myserverArg, + std::string groupNameArg, + size_t msgNumArg, + std::string uidArg, + mail::readMode getType); + + ~FetchTask(); + void fetchedText(std::string); +}; + + +LIBMAIL_END + +#endif diff --git a/libmail/nntpfolder.C b/libmail/nntpfolder.C new file mode 100644 index 0000000..689b0c2 --- /dev/null +++ b/libmail/nntpfolder.C @@ -0,0 +1,554 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "nntpfolder.H" +#include "nntpnewsrc.H" +#include "nntplistactive.H" +#include "nntpgroupinfo.H" +#include "nntpgroupopen.H" +#include "nntpadd.H" +#include <fstream> +#include <vector> +#include <list> +#include <map> +#include <errno.h> +#include <cstring> + +using namespace std; + +mail::nntp::folder::folder(nntp *myserverArg, string pathArg, + bool hasMessagesArg, bool hasSubFoldersArg) + : mail::folder(myserverArg), myserver(myserverArg), + path(pathArg), hasMessagesFlag(hasMessagesArg), + hasSubFoldersFlag(hasSubFoldersArg) +{ + size_t p=pathArg.rfind('.'); + + if (p == std::string::npos) + p=0; + else + ++p; + + name=pathArg.substr(p); + + if (strncasecmp(pathArg.c_str(), FOLDER_SUBSCRIBED ".", + sizeof(FOLDER_SUBSCRIBED)) == 0) + name=pathArg.substr(sizeof(FOLDER_SUBSCRIBED)); + + if (path == FOLDER_SUBSCRIBED) + name="Subscribed newsgroups"; + if (path == FOLDER_NEWSRC) + name="All newsgroups"; + if (path == FOLDER_CHECKNEW) + name="Check for new newsgroups"; + if (path == FOLDER_REFRESH) + name="Refresh newsgroup list"; + +} + +mail::nntp::folder::folder(const mail::nntp::folder &o) + : mail::folder(o.myserver), myserver(o.myserver), + path(o.path), name(o.name), hasMessagesFlag(o.hasMessagesFlag), + hasSubFoldersFlag(o.hasSubFoldersFlag) +{ +} + +mail::nntp::folder &mail::nntp::folder::operator=(const mail::nntp::folder &o) +{ + path=o.path; + name=o.name; + hasMessagesFlag=o.hasMessagesFlag; + hasSubFoldersFlag=o.hasSubFoldersFlag; + + return *this; +} + +mail::nntp::folder::~folder() +{ +} + +void mail::nntp::folder::sameServerAsHelperFunc() const +{ +} + +std::string mail::nntp::folder::getName() const +{ + return name; +} + +std::string mail::nntp::folder::getPath() const +{ + return path; +} + +bool mail::nntp::folder::hasMessages() const +{ + return hasMessagesFlag; +} + +bool mail::nntp::folder::hasSubFolders() const +{ + return hasSubFoldersFlag; +} + +bool mail::nntp::folder::isParentOf(std::string p) const +{ + string s=path + "."; + + if (p.size() >= s.size() && p.substr(0, s.size()) == s) + return true; + return false; +} + +void mail::nntp::folder::hasMessages(bool flag) +{ + hasMessagesFlag=flag; +} + +void mail::nntp::folder::hasSubFolders(bool flag) +{ + hasSubFoldersFlag=flag; +} + +string mail::nntp::folder::newsgroupName() const +{ + string s=path; + + if (s.size() > 0 && s[0] == '/') + { + size_t n=s.find('.'); + + if (n == std::string::npos) + s=""; + else + s=s.substr(n+1); + } + + if (s.size() > 0 && s.find('\r') == std::string::npos + && s.find('\n') == std::string::npos) + return s; + return ""; +} + +string mail::nntp::folder::isNewsgroup() const +{ + return newsgroupName(); +} + +void mail::nntp::folder::readFolderInfo( callback::folderInfo + &callback1, + callback &callback2) const +{ + if (isDestroyed(callback2)) + return; + + string s=newsgroupName(); + + if (s.size() == 0) + { + callback2.success("Ok."); + return; + } + + myserver->installTask(new GroupInfoTask(&callback2, *myserver, + s, callback1)); +} + +void mail::nntp::folder::getParentFolder(callback::folderList &callback1, + callback &callback2) const +{ + if (isDestroyed(callback2)) + return; + + string ppath(""); + + size_t n=path.rfind('.'); + + if (n != std::string::npos) + ppath=path.substr(0, n); + + myserver->findFolder(ppath, callback1, callback2); +} + +void mail::nntp::folder::readSubFolders( callback::folderList &callback1, + callback &callback2) const +{ + if (isDestroyed(callback2)) + return; + + if (path.size() == 0) + { + myserver->readTopLevelFolders(callback1, callback2); + return; + } + + if (path == FOLDER_REFRESH) + { + myserver->installTask(new ListActiveTask(&callback2, + *myserver)); + return; + } + + list<mail::nntp::folder> folderList; + + if (path == FOLDER_CHECKNEW) + { + if (!myserver->hasNewgroups) + { + ifstream i(myserver->newsrcFilename.c_str()); + string serverDate; + + if (i.is_open()) + { + string line; + + while (!getline(i, line).fail()) + { + if (line.substr(0, 7) == "#DATE: ") + { + serverDate=line.substr(7); + break; + } + } + } + + if (serverDate.size() == 14) + { + myserver-> + installTask(new + ListActiveTask(&callback2, + *myserver, + serverDate, + &callback1) + ); + return; + } + } + + vector<string>::iterator b=myserver->newgroups.begin(), + e=myserver->newgroups.end(); + + while (b != e) + { + folderList.insert(folderList.end(), + folder(myserver, + FOLDER_SUBSCRIBED "." + + *b, true, false)); + b++; + } + } + else if (path == FOLDER_SUBSCRIBED) + { + // List all subscribed folders, in a flat hierarchy + + ifstream i(myserver->newsrcFilename.c_str()); + + if (i.is_open()) + { + string line; + + while (!getline(i, line).fail()) + { + newsrc parseLine(line); + + if (parseLine.newsgroupname.size() == 0) + continue; + + if (!parseLine.subscribed) + continue; + + folderList + .insert(folderList.end(), + folder(myserver, + FOLDER_SUBSCRIBED "." + + parseLine + .newsgroupname, + true, + false)); + } + } + } + else if (path.substr(0, sizeof(FOLDER_NEWSRC)-1) == FOLDER_NEWSRC) + { + size_t p=path.find('.'); + string s=p == std::string::npos ? "":path.substr(p+1); + string sdot=s + "."; + + // All newsrc folders, hierarchically + + map<string, hierEntry> subfolders; + + ifstream i(myserver->newsrcFilename.c_str()); + + if (i.is_open()) + { + string line; + + while (!getline(i, line).fail()) + { + newsrc parseLine(line); + + if (parseLine.newsgroupname.size() == 0) + continue; + + string n; + + // Take only those newsrc newsgroup that + // are in the hierarchy we're opening + + if (s.size() == 0) // Top hierarchy + { + n=parseLine.newsgroupname; + } + else + { + if (parseLine.newsgroupname.size() + < sdot.size() || + parseLine.newsgroupname + .substr(0, sdot.size()) + != sdot) + continue; // Not in this hier + + n=parseLine.newsgroupname + .substr(sdot.size()); + } + + bool isSubFolder=false; + + p=n.find('.'); + + if (p != std::string::npos) + { + n=n.substr(0, p); + isSubFolder=true; + } + + map<string, hierEntry>::iterator p= + subfolders + .insert( make_pair(n, + hierEntry())) + .first; + + if (isSubFolder) + p->second.foundSubFolders=true; + else + p->second.foundFolder=true; + } + } + i.close(); + + map<string, hierEntry>::iterator b, e; + b=subfolders.begin(); + e=subfolders.end(); + + while (b != e) + { + if (b->second.foundSubFolders) + folderList.insert(folderList.end(), + folder(myserver, + path + "." + b->first, + false, true)); + + if (b->second.foundFolder) + { + string s=path + "." + b->first; + + size_t n=s.find('.'); + + folderList + .insert(folderList.end(), + folder(myserver, + FOLDER_SUBSCRIBED + + s.substr(n), + true, + false)); + } + b++; + } + } + + // Convert the folder list to vector of pointers, then invoke the + // callbacks. + + vector<const mail::folder *> ptrList; + + list<mail::nntp::folder>::iterator b, e; + + b=folderList.begin(); + e=folderList.end(); + + while (b != e) + { + ptrList.push_back( &*b ); + b++; + } + + callback1.success(ptrList); + callback2.success("Ok."); +} + +mail::addMessage *mail::nntp::folder::addMessage(callback &callback) const +{ + if (isDestroyed(callback)) + return NULL; + + return new mail::nntp::add(myserver, &callback); +} + +void mail::nntp::folder::createSubFolder(std::string name, bool isDirectory, + callback::folderList + &callback1, + callback &callback2) const +{ + callback2.fail("Not implemented"); +} + +void mail::nntp::folder::create(bool isDirectory, + callback &callback) const +{ + callback.fail("Not implemented"); +} + +// +// A request to delete a folder translate to an unsubscribe request. +// + +void mail::nntp::folder::destroy(callback &callback, bool destroyDir) const +{ + if (isDestroyed(callback)) + return; + + string s=newsgroupName(); + + if (s.size() == 0) + { + callback.fail("Not implemented"); + return; + } + + myserver->updateCachedNewsrc(); + myserver->discardCachedNewsrc(); + + string newNewsrcFilename=myserver->newsrcFilename + ".tmp"; + + ofstream o(newNewsrcFilename.c_str()); + + if (o.is_open()) + { + ifstream i(myserver->newsrcFilename.c_str()); + + string line; + + while (i.is_open() && !getline(i, line).fail()) + { + newsrc parseLine(line); + + if (parseLine.newsgroupname.size() == 0) + { + o << line << endl; + continue; + } + + if (parseLine.newsgroupname == s) + { + parseLine.subscribed=false; + } + + o << (string)parseLine << endl; + } + + if (!o.fail() && !o.bad() && + rename(newNewsrcFilename.c_str(), + myserver->newsrcFilename.c_str()) == 0) + { + callback.success("Unsubscribed."); + return; + } + } + callback.fail(strerror(errno)); +} + +void mail::nntp::folder::renameFolder(const mail::folder *newParent, + string newName, + callback::folderList &callback1, + callback &callback2) const +{ + callback2.fail("Not implemented"); +} + +mail::folder *mail::nntp::folder::clone() const +{ + if (isDestroyed()) + { + errno=EIO; + return NULL; + } + + return new folder(myserver, path, hasMessagesFlag, + hasSubFoldersFlag); +} + +string mail::nntp::folder::toString() const +{ + string s=""; + + int i; + + for (i=0; i<3; i++) + { + string ss; + + if (i > 0) + s += ":"; + + switch (i) { + case 0: + if (hasMessagesFlag) ss += "F"; + if (hasSubFoldersFlag) ss += "D"; + break; + case 1: + ss=name; + break; + case 2: + ss=path; + break; + } + + string::iterator b, e; + b=ss.begin(); + e=ss.end(); + + while (b != e) + { + if (*b == '\\' || *b == ':') + s.append("\\", 1); + s.append(&*b, 1); + b++; + } + } + return s; +} + +void mail::nntp::folder::open(callback &openCallback, + snapshot *restoreSnapshot, + callback::folder &folderCallback) const +{ + if (isDestroyed(openCallback)) + return; + + string s=newsgroupName(); + + if (s.size() > 0) + { + myserver->installTask(new GroupOpenTask(&openCallback, + *myserver, + s, + restoreSnapshot, + &folderCallback)); + return; + } + + openCallback.fail("Not implemented"); +} diff --git a/libmail/nntpfolder.H b/libmail/nntpfolder.H new file mode 100644 index 0000000..ccd7a7b --- /dev/null +++ b/libmail/nntpfolder.H @@ -0,0 +1,100 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_nntpfolder_H +#define libmail_nntpfolder_H + +#include "libmail_config.h" + +#include "nntp.H" + +LIBMAIL_START + +// An NNTP folder object + +class mail::nntp::folder : public mail::folder { + + nntp *myserver; + + std::string path; + std::string name; + + bool hasMessagesFlag; + bool hasSubFoldersFlag; + + // Private class used to build hierarchy listings + + std::string newsgroupName() const; + +public: + class hierEntry { + public: + bool foundFolder; + bool foundSubFolders; + + hierEntry() : foundFolder(false), foundSubFolders(false) + { + } + + ~hierEntry() + { + } + }; + + folder(nntp *myserverArg, std::string pathArg, + bool hasMessagesArg, bool hasSubFoldersArg); + + folder(const mail::nntp::folder &); + mail::nntp::folder &operator=(const mail::nntp::folder &); + + ~folder(); + + void sameServerAsHelperFunc() const; + std::string getName() const; + std::string getPath() const; + bool hasMessages() const; + bool hasSubFolders() const; + std::string isNewsgroup() const; + bool isParentOf(std::string path) const; + void hasMessages(bool); + void hasSubFolders(bool); + void readFolderInfo( callback::folderInfo + &callback1, + callback &callback2) const; + void getParentFolder(callback::folderList &callback1, + callback &callback2) const; + void readSubFolders( callback::folderList &callback1, + callback &callback2) const; + mail::addMessage *addMessage(callback &callback) const; + void createSubFolder(std::string name, bool isDirectory, + callback::folderList + &callback1, + callback &callback2) const; + void create(bool isDirectory, + callback &callback) const; + + void destroy(callback &callback, bool destroyDir) const; + void renameFolder(const mail::folder *newParent, std::string newName, + callback::folderList &callback1, + callback &callback2) const; + mail::folder *clone() const; + std::string toString() const; + void open(callback &openCallback, + snapshot *restoreSnapshot, + callback::folder &folderCallback) const; +}; + +// Special folder paths + +#define FOLDER_SUBSCRIBED "/subscribed" // Subscribed newsrc folders + +#define FOLDER_NEWSRC "/newsrc" // All newsgroups in newsrc + +#define FOLDER_CHECKNEW "/newgroups" // New newsgroups + +#define FOLDER_REFRESH "/refresh" // Refresh newsgroup list + +LIBMAIL_END +#endif diff --git a/libmail/nntpgroup.C b/libmail/nntpgroup.C new file mode 100644 index 0000000..99cbf47 --- /dev/null +++ b/libmail/nntpgroup.C @@ -0,0 +1,91 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "nntpgroup.H" +#include <sstream> + +using namespace std; + +// +// Make sure a particular group is selected, before proceeding +// + +mail::nntp::GroupTask::GroupTask(callback *callbackArg, nntp &myserverArg, + std::string groupNameArg) + : LoggedInTask(callbackArg, myserverArg), + groupName(groupNameArg) +{ +} + +mail::nntp::GroupTask::~GroupTask() +{ +} + +void mail::nntp::GroupTask::loggedIn() +{ + if (myserver->serverGroup == groupName && + myserver->openedGroup == groupName) + // Must check openedGroup + { + selectedCurrentGroup(); + return; + } + + response_func= &mail::nntp::GroupTask::processGroupStatus; + myserver->serverGroup=""; + myserver->socketWrite("GROUP " + groupName + "\r\n"); +} + +void mail::nntp::GroupTask::processLine(const char *message) +{ + (this->*response_func)(message); +} + +void mail::nntp::GroupTask::selectedCurrentGroup() +{ + response_func= &mail::nntp::GroupTask::processOtherStatus; + if (myserver->index.size() == 0) + selectedGroup(0, 0, 0); + else + selectedGroup(myserver->index[0].msgNum, + myserver->index.end()[-1].msgNum, + myserver->index.size()); +} + +void mail::nntp::GroupTask::processGroupStatus(const char *msg) +{ + string buf=msg; + istringstream i(buf); + + int status; + msgnum_t est, lo, hi; + + i >> status >> est >> lo >> hi; + + if (i.bad() || i.fail() || (status / 100) != 2) + { + fail(msg); + return; + } + + ++hi; + + if (est > hi-lo) + est=hi-lo; // Fix an impossibility + + + response_func= &mail::nntp::GroupTask::processOtherStatus; + myserver->serverGroup=groupName; + + if (myserver->openedGroup == groupName) + selectedCurrentGroup(); + else + selectedGroup(est, lo, hi); +} + +void mail::nntp::GroupTask::processOtherStatus(const char *msg) +{ + processGroup(msg); +} diff --git a/libmail/nntpgroup.H b/libmail/nntpgroup.H new file mode 100644 index 0000000..ebd2cae --- /dev/null +++ b/libmail/nntpgroup.H @@ -0,0 +1,56 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_nntpgroup_H +#define libmail_nntpgroup_H + +#include "libmail_config.h" + +#include "nntp.H" +#include "nntplogin.H" + +#include <string> + +LIBMAIL_START + +// +// Open a specific group + + +class mail::nntp::GroupTask : public mail::nntp::LoggedInTask { + + void (mail::nntp::GroupTask::*response_func)(const char *); + +protected: + std::string groupName; + +public: + + GroupTask(callback *callbackArg, nntp &myserverArg, + std::string groupNameArg); + ~GroupTask(); + + // Implements: + + void loggedIn(); + void processLine(const char *message); + + // Subclasses must defined: + + virtual void selectedGroup(msgnum_t estimatedCount, + msgnum_t loArticleCount, + msgnum_t hiArticleCount)=0; + virtual void processGroup(const char *)=0; + +private: + void processGroupStatus(const char *); + void processOtherStatus(const char *); + + void selectedCurrentGroup(); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/nntpgroupinfo.C b/libmail/nntpgroupinfo.C new file mode 100644 index 0000000..bf8392a --- /dev/null +++ b/libmail/nntpgroupinfo.C @@ -0,0 +1,82 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ + +#include "nntpgroupinfo.H" +#include "nntpnewsrc.H" + +using namespace std; + +mail::nntp::GroupInfoTask::GroupInfoTask(callback *callbackArg, + nntp &myserverArg, + std::string groupNameArg, + callback::folderInfo &infoCallbackArg) + : GroupTask(callbackArg, myserverArg, groupNameArg), + infoCallback(infoCallbackArg) +{ +} + +mail::nntp::GroupInfoTask::~GroupInfoTask() +{ +} + +void mail::nntp::GroupInfoTask::selectedGroup(msgnum_t estimatedCount, + msgnum_t loArticleCount, + msgnum_t hiArticleCount) +{ + if (groupName == myserver->openedGroup) + { + infoCallback.messageCount=myserver->index.size(); + success("Ok"); + return; + } + + myserver->cacheNewsrc(); + + // Subtract articles marked as read, from the estimated count + + map<string, newsrc>::iterator p + =myserver->cachedNewsrc.find(myserver->serverGroup); + + if (p != myserver->cachedNewsrc.end()) + { + estimatedCount=hiArticleCount - loArticleCount; + + vector< pair<msgnum_t, msgnum_t> >::iterator + c=p->second.msglist.begin(), + e=p->second.msglist.end(); + + while (c != e) + { + size_t a=c->first; + size_t b=c->second+1; + + if (b > loArticleCount && a < hiArticleCount) + { + if (a < loArticleCount) + a=loArticleCount; + + if (b > hiArticleCount) + b=hiArticleCount; + + b -= a; + + if (b < estimatedCount) + estimatedCount -= b; + else + estimatedCount=0; + } + c++; + } + } + + infoCallback.messageCount=estimatedCount; + + success("Ok"); +} + +void mail::nntp::GroupInfoTask::processGroup(const char *msg) +{ +} diff --git a/libmail/nntpgroupinfo.H b/libmail/nntpgroupinfo.H new file mode 100644 index 0000000..00dea1f --- /dev/null +++ b/libmail/nntpgroupinfo.H @@ -0,0 +1,40 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_nntpgroupinfo_H +#define libmail_nntpgroupinfo_H + +#include "libmail_config.h" + +#include "nntp.H" +#include "nntpgroup.H" + +#include <string> + +LIBMAIL_START + +// +// Obtain number of messages in the group by using a GROUP +// + +class mail::nntp::GroupInfoTask : public mail::nntp::GroupTask { + callback::folderInfo &infoCallback; + +public: + + GroupInfoTask(callback *callbackArg, nntp &myserverArg, + std::string groupNameArg, + callback::folderInfo &infoCallbackArg); + ~GroupInfoTask(); + + void selectedGroup(msgnum_t estimatedCount, + msgnum_t loArticleCount, + msgnum_t hiArticleCount); + void processGroup(const char *); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/nntpgroupopen.C b/libmail/nntpgroupopen.C new file mode 100644 index 0000000..70345bb --- /dev/null +++ b/libmail/nntpgroupopen.C @@ -0,0 +1,331 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "nntp.H" +#include "nntpgroupopen.H" +#include "nntpchecknew.H" +#include "nntpnewsrc.H" +#include <sstream> +#include <fstream> +#include <errno.h> +#include <cstring> + +using namespace std; + +///////////////////////////////////////////////////////////////////////////// +// +// Helper class for restoring application-saved snapshots + +mail::nntp::GroupOpenTask::snapshotRestoreHelper +::snapshotRestoreHelper(mail::nntp &nntpArg) + : orignntp(nntpArg), abortedFlag(false) +{ +} + +mail::nntp::GroupOpenTask::snapshotRestoreHelper::~snapshotRestoreHelper() +{ +} + +void mail::nntp::GroupOpenTask::snapshotRestoreHelper +::restoreIndex(size_t msgNum, + const mail::messageInfo &mi) +{ + if (msgNum >= index.size()) + return; + + istringstream i(mi.uid); + msgnum_t n=0; + + i >> n; + + if (i.fail() || n == 0) + return; + + index[msgNum].msgNum=n; + + unsigned char flags=0; + + if (mi.deleted) + flags |= IDX_DELETED; + + if (mi.marked) + flags |= IDX_FLAGGED; + + index[msgNum].msgFlag=flags; +} + +void mail::nntp::GroupOpenTask::snapshotRestoreHelper +::restoreKeywords(size_t msgNum, + const std::set<std::string> &kwSet) +{ + if (msgNum >= index.size()) + return; + + index[msgNum].keywords.setFlags(orignntp.keywordHashtable, kwSet); +} + +void mail::nntp::GroupOpenTask::snapshotRestoreHelper::abortRestore() +{ + abortedFlag=true; +} + +///////////////////////////////////////////////////////////////////////////// + +mail::nntp::GroupOpenTask::GroupOpenTask(callback *callbackArg, + nntp &myserverArg, + std::string groupNameArg, + snapshot *restoreSnapshotArg, + callback::folder *folderCallbackArg) + : GroupTask(callbackArg, myserverArg, groupNameArg), + folderCallback(folderCallbackArg), + groupName(groupNameArg), + restoreSnapshot(restoreSnapshotArg) +{ +} + +mail::nntp::GroupOpenTask::~GroupOpenTask() +{ +} + +void mail::nntp::GroupOpenTask::selectedGroup(msgnum_t estimatedCount, + msgnum_t loArticleCount, + msgnum_t hiArticleCount) +{ + myserver->openedGroup=""; + myserver->cleartmp(); + myserver->index.clear(); + myserver->index.reserve(estimatedCount); + myserver->loWatermark=loArticleCount; + myserver->hiWatermark=hiArticleCount; + + map<std::string, newsrc>::iterator p; + + if (myserver->didCacheNewsrc && + (p=myserver->cachedNewsrc.find(groupName)) != + myserver->cachedNewsrc.end()) + { + myNewsrc=p->second; + } + else + { + myNewsrc.newsgroupname=groupName; + myNewsrc.subscribed=false; + + ifstream i(myserver->newsrcFilename.c_str()); + + string line; + + while (i.is_open() && !getline(i, line).fail()) + { + newsrc parseLine(line); + + if (parseLine.newsgroupname == groupName) + { + myNewsrc=parseLine; + break; + } + } + } + + if (!myNewsrc.subscribed) // Subscribed now, clear deleted list. + { + myNewsrc.subscribed=true; + myNewsrc.msglist.clear(); + } + + if (myNewsrc.msglist.size() > 0 && + myNewsrc.msglist.end()[-1].second > hiArticleCount) + { + myNewsrc.msglist.clear(); // Group renumbered on the swerver + } + + msglistI=myNewsrc.msglist.begin(); + + string cmd="LISTGROUP\r\n"; + + if (restoreSnapshot) + { + size_t cnt; + string dummy; + msgnum_t cLo=0, cHi=0; + + restoreSnapshot->getSnapshotInfo(dummy, cnt); + + if (dummy.size() > 0) + { + istringstream iss(dummy); + + char dummyChar; + + iss >> cLo >> dummyChar >> cHi; + + if (iss.fail() || cLo == 0 || dummyChar != '-' || + cHi < cLo || + cHi > myserver->hiWatermark + // Server probably renumbered + ) + { + dummy=""; + } + } + + if (dummy.size() > 0) + { + snapshotRestoreHelper myHelper(*myserver); + + myHelper.index.resize(cnt); + + restoreSnapshot->restoreSnapshot(myHelper); + + if (!myHelper.abortedFlag) + { + // Sanity check + + vector<nntpMessageInfo>::iterator + b=myHelper.index.begin(), + e=myHelper.index.end(); + + bool first=true; + + while (b != e) + { + if (b->msgNum == 0 || + (!first && + b[-1].msgNum >= b->msgNum)) + { + myHelper.abortedFlag=true; + break; + } + b++; + first=false; + } + } + if (!myHelper.abortedFlag) + { + size_t i; + size_t n=myHelper.index.size(); + + for (i=0; i<n; i++) + { + if (myHelper.index[i].msgNum < + myserver->loWatermark) + continue; + + processMessageNumber(myHelper + .index[i].msgNum, + myHelper + .index[i].msgFlag, + myHelper + .index[i].keywords); + } + + ostringstream o; + + o << "XHDR Lines " << cHi << "-\r\n"; + cmd=o.str(); + } + } + } + + response_func= &mail::nntp::GroupOpenTask::processLISTGROUP; + myserver->socketWrite(cmd); +} + +void mail::nntp::GroupOpenTask::processGroup(const char *msg) +{ + (this->*response_func)(msg); +} + +void mail::nntp::GroupOpenTask::processLISTGROUP(const char *msg) +{ + if (msg[0] == '4') + { + response_func= + &mail::nntp::GroupOpenTask::processMessageNumber; + + processMessageNumber("."); + return; + } + + if (msg[0] != '2') + { + fail(msg); + return; + } + + response_func= &mail::nntp::GroupOpenTask::processMessageNumber; +} + + +bool mail::nntp::GroupOpenTask::opened() +{ + myserver->folderCallback=folderCallback; + myserver->openedGroup=groupName; + myserver->createNewsrc(myNewsrc); + + if (!myserver->updateOpenedNewsrc(myNewsrc)) + { + fail(strerror(errno)); + return false; + } + return true; +} + +void mail::nntp::GroupOpenTask::processMessageNumber(const char *msg) +{ + if (strcmp(msg, ".") == 0) + { + if (!opened()) + return; + myserver->saveSnapshot(); + + ostringstream o; + + o << groupName << ": " << myserver->index.size() + << " messages."; + + success(o.str()); + return; + } + + string s(msg); + istringstream i(s); + + msgnum_t n; + + i >> n; + + if (!i.bad() && !i.fail()) + { + mail::keywords::Message kw; + + processMessageNumber(n, 0, kw); + } +} + +void mail::nntp::GroupOpenTask::processMessageNumber(msgnum_t n, + unsigned char flags, + mail::keywords::Message + &kw) +{ + while (msglistI != myNewsrc.msglist.end() && + msglistI->second < n) + msglistI++; + + if (msglistI != myNewsrc.msglist.end() && + msglistI->first <= n) + return; // This message is excluded + + if (myserver->index.size() > 0 && + n <= myserver->index.end()[-1].msgNum) + return; // Bad server mojo + + nntpMessageInfo newInfo; + + newInfo.msgNum=n; + newInfo.msgFlag=flags; + newInfo.keywords=kw; + myserver->index.push_back(newInfo); +} diff --git a/libmail/nntpgroupopen.H b/libmail/nntpgroupopen.H new file mode 100644 index 0000000..a568f1b --- /dev/null +++ b/libmail/nntpgroupopen.H @@ -0,0 +1,74 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_nntpgroupopen_H +#define libmail_nntpgroupopen_H + +#include "libmail_config.h" + +#include "nntp.H" +#include "nntpgroup.H" +#include "nntpnewsrc.H" +#include "snapshot.H" + +#include <string> + +LIBMAIL_START + +// +// Officially open a newsgruop +// + +class mail::nntp::GroupOpenTask : public mail::nntp::GroupTask { + + callback::folder *folderCallback; + std::string groupName; + snapshot *restoreSnapshot; + + void (mail::nntp::GroupOpenTask::*response_func)(const char *); + + newsrc myNewsrc; + std::vector< std::pair<msgnum_t, msgnum_t> >::iterator msglistI; + + class snapshotRestoreHelper : public mail::snapshot::restore { + mail::nntp &orignntp; + public: + bool abortedFlag; + + std::vector<nntpMessageInfo> index; + + snapshotRestoreHelper(mail::nntp &nntpArg); + ~snapshotRestoreHelper(); + void restoreIndex(size_t msgNum, + const mail::messageInfo &); + void restoreKeywords(size_t msgNum, + const std::set<std::string> &); + void abortRestore(); + }; + +public: + + GroupOpenTask(callback *callbackArg, nntp &myserverArg, + std::string groupNameArg, + snapshot *restoreSnapshotArg, + callback::folder *folderCallbackArg); + ~GroupOpenTask(); + + void selectedGroup(msgnum_t estimatedCount, + msgnum_t loArticleCount, + msgnum_t hiArticleCount); + void processGroup(const char *); + +private: + void processLISTGROUP(const char *); + void processMessageNumber(const char *); + void processMessageNumber(msgnum_t n, unsigned char flags, + mail::keywords::Message &keywords); + bool opened(); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/nntplistactive.C b/libmail/nntplistactive.C new file mode 100644 index 0000000..218c9cf --- /dev/null +++ b/libmail/nntplistactive.C @@ -0,0 +1,224 @@ +/* +** Copyright 2003-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "nntplistactive.H" +#include "nntpfolder.H" +#include "nntpnewsrc.H" +#include <sstream> +#include <algorithm> +#include <errno.h> +#include <cstring> + +using namespace std; + +mail::nntp::ListActiveTask::ListActiveTask(callback *callbackArg, + nntp &myserverArg) + : LoggedInTask(callbackArg, myserverArg), count(0), + folderListCallback(NULL), cmd(LIST) +{ +} + +mail::nntp::ListActiveTask::ListActiveTask(callback *callbackArg, + nntp &myserverArg, + string dateArg, + callback::folderList + *folderListCallbackArg) + : LoggedInTask(callbackArg, myserverArg), serverDate(dateArg), + count(0), folderListCallback(folderListCallbackArg), + cmd(NEWGROUPS) +{ +} + +mail::nntp::ListActiveTask::~ListActiveTask() +{ +} + +void mail::nntp::ListActiveTask::loggedIn() +{ + response_func= &mail::nntp::ListActiveTask::processDateStatus; + myserver->socketWrite("DATE\r\n"); +} + +void mail::nntp::ListActiveTask::processLine(const char *message) +{ + (this->*response_func)(message); +} + +void mail::nntp::ListActiveTask::processDateStatus(const char *msg) +{ + string prevDate=serverDate; + + if (msg[0] == '1') + { + while (*msg && !unicode_isspace((unsigned char)*msg)) + msg++; + + while (*msg && unicode_isspace((unsigned char)*msg)) + msg++; + + serverDate=msg; + } + + response_func= &mail::nntp::ListActiveTask::processListStatus; + + switch (cmd) { + case NEWGROUPS: + myserver->socketWrite("NEWGROUPS " + + prevDate.substr(0,8) + " " + + prevDate.substr(8) + " GMT\r\n"); + break; + default: + myserver->socketWrite("LIST\r\n"); + } +} + +void mail::nntp::ListActiveTask::processListStatus(const char *msg) +{ + if (msg[0] != '2') + { + fail(msg); + return; + } + response_func= &mail::nntp::ListActiveTask::processSubscription; +} + +void mail::nntp::ListActiveTask::processSubscription(const char *msg) +{ + if (strcmp(msg, ".")) + { + const char *p=msg; + + while (*p && !unicode_isspace((unsigned char)*p)) + p++; + + string newsgName=string(msg, p); + msgnum_t lo, hi; + + string msgRange=p; + istringstream i(msgRange); + + i >> lo >> hi; + + if (!i.fail()) + { + newsgroupList.insert(newsgName); + + if ((++count % 100) == 0 && callbackPtr) + { + callbackPtr->reportProgress(0, 0, count, 0); + } + } + return; + } + + myserver->updateCachedNewsrc(); + myserver->discardCachedNewsrc(); + + string newNewsrcFilename=myserver->newsrcFilename + ".tmp"; + + size_t newGroupCount=0; + size_t delGroupCount=0; + + { + ifstream i(myserver->newsrcFilename.c_str()); + + ofstream o(newNewsrcFilename.c_str()); + + if (o.is_open()) + { + if (serverDate.size() > 0) + o << "#DATE: " << serverDate << endl; + + string line; + + // Copy old newsrc to new newsrc + // Removed groups are skipped. Found groups are removed + // from newsgroupList, ending up with new newsgroups + + while (i.is_open() && !getline(i, line).fail()) + { + newsrc parseLine(line); + + if (parseLine.newsgroupname.size() == 0) + continue; + + set<string>::iterator n=newsgroupList + .find(parseLine.newsgroupname); + + if (n == newsgroupList.end()) + { + if (cmd != NEWGROUPS) + { + ++delGroupCount; + continue; + } + } + else + newsgroupList.erase(n); + + o << (string)parseLine << endl; + } + + vector<string> sortedNewsgroupList; + + sortedNewsgroupList.insert(sortedNewsgroupList.end(), + newsgroupList.begin(), + newsgroupList.end()); + newsgroupList.clear(); + + sort(sortedNewsgroupList.begin(), + sortedNewsgroupList.end()); + + vector<string>::iterator b=sortedNewsgroupList.begin(), + e=sortedNewsgroupList.end(); + + while (b != e) + { + o << *b << "!" << endl; + ++newGroupCount; + b++; + } + + i.close(); + o << flush; + o.close(); + + if (cmd == NEWGROUPS) + { + myserver->newgroups=sortedNewsgroupList; + myserver->hasNewgroups=true; + } + } + + if (o.fail() || o.bad() || + rename(newNewsrcFilename.c_str(), + myserver->newsrcFilename.c_str()) < 0) + { + string msg=strerror(errno); + fail(msg); + return; + } + } + + if (cmd == NEWGROUPS && callbackPtr && myserver->hasNewgroups) + { + callback *c=callbackPtr; + + callbackPtr=NULL; + + mail::nntp::folder fakeFolder(myserver, + FOLDER_CHECKNEW, + false, true); + + fakeFolder.readSubFolders(*folderListCallback, *c); + } + + ostringstream o; + + o << count << " groups, " << newGroupCount << " new, " + << delGroupCount << " removed."; + + success(o.str()); +} diff --git a/libmail/nntplistactive.H b/libmail/nntplistactive.H new file mode 100644 index 0000000..eed13d8 --- /dev/null +++ b/libmail/nntplistactive.H @@ -0,0 +1,63 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_nntplistactive_H +#define libmail_nntplistactive_H + +#include "libmail_config.h" +#include <fstream> + +#include "nntp.H" +#include "nntplogin.H" + +#include <set> +#include <string> + +LIBMAIL_START + +// Issue a LIST ACTIVE, update newsrc accordingly + +class mail::nntp::ListActiveTask : public mail::nntp::LoggedInTask { + + void (mail::nntp::ListActiveTask::*response_func)(const char *); + + std::string serverDate; + + std::set<std::string> newsgroupList; + size_t count; + + callback::folderList *folderListCallback; + +public: + enum cmdType { + LIST, + NEWGROUPS + }; +private: + cmdType cmd; +public: + + ListActiveTask(callback *callbackArg, nntp &myserverArg); + // A regular, garden-variety LIST+resync newsrc + ~ListActiveTask(); + + ListActiveTask(callback *callbackArg, nntp &myserverArg, + std::string sinceDate, + callback::folderList *); + + // Do a NEWGROUPS + + void loggedIn(); + void processLine(const char *message); + +private: + void processDateStatus(const char *); + void processListStatus(const char *); + void processSubscription(const char *); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/nntplogin.C b/libmail/nntplogin.C new file mode 100644 index 0000000..211875f --- /dev/null +++ b/libmail/nntplogin.C @@ -0,0 +1,212 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ + +#include "nntplogin.H" + +using namespace std; + +mail::nntp::LoggedInTask::LoggedInTask(mail::callback *callbackArg, + mail::nntp &myserverArg) + : Task(callbackArg, myserverArg), loggingIn(true) +{ +} + +mail::nntp::LoggedInTask::~LoggedInTask() +{ +} + +void mail::nntp::LoggedInTask::installedTask() +{ + if (myserver->getfd() >= 0) // Already logged in + { + loginCompleted2(); + return; + } + + myserver->serverGroup=""; + responseFunc= &mail::nntp::LoggedInTask::doGreeting; + myserver->nntpLoginInfo=myserver->savedLoginInfo; + + + if (myserver->nntpLoginInfo.uid.size() > 0 && + myserver->nntpLoginInfo.pwd.size() == 0 && + myserver->nntpLoginInfo.loginCallbackFunc) + { + currentCallback=myserver->nntpLoginInfo.loginCallbackFunc; + currentCallback->target=this; + defaultTimeout=300; + resetTimeout(); + myserver->nntpLoginInfo.loginCallbackFunc->getPassword(); + return; + } + gotPassword(myserver->nntpLoginInfo.pwd); +} + +void mail::nntp::LoggedInTask::gotPassword(std::string pwd) +{ + defaultTimeout=myserver->timeoutSetting; + resetTimeout(); + + myserver->nntpLoginInfo.pwd= + myserver->savedLoginInfo.pwd=pwd; + + myserver->savedLoginInfo.loginCallbackFunc=NULL; + + if (myserver->nntpLoginInfo.pwd.find('\n') != + std::string::npos || + myserver->nntpLoginInfo.pwd.find('\r') != + std::string::npos || + myserver->nntpLoginInfo.uid.find('\n') != + std::string::npos || + myserver->nntpLoginInfo.uid.find('\r') != + std::string::npos) + { + fail("Invalid characters in userid or password"); + return; + } + + string errmsg=myserver-> + socketConnect(myserver->nntpLoginInfo, "nntp", "nntps"); + + if (errmsg.size() > 0) + { + fail(errmsg.c_str()); + return; + } +} + +// Make sure the socket gets closed, if we fail. + +void mail::nntp::LoggedInTask::fail(std::string msg) +{ + if (loggingIn) + myserver->socketDisconnect(); + Task::fail(msg); +} + +void mail::nntp::LoggedInTask::doFwdResponse(const char *message) +{ + processLine(message); +} + +void mail::nntp::LoggedInTask::serverResponse(const char *message) +{ + (this->*responseFunc)(message); +} + +void mail::nntp::LoggedInTask::doGreeting(const char *message) +{ + if (*message != '2') + { + myserver->socketDisconnect(); + fail(message); + return; + } + + responseFunc= &mail::nntp::LoggedInTask::doModeReader1; + + myserver->socketWrite("MODE READER\r\n"); +} + +void mail::nntp::LoggedInTask::doModeReader1(const char *message) +{ + if (myserver->nntpLoginInfo.uid.size() > 0 && + myserver->nntpLoginInfo.pwd.size() > 0) + { + authinfoUser(); + return; + } + + loginCompleted(); +} + +// Received uid/pwd from the app. +void mail::nntp::LoggedInTask::loginInfoCallback(std::string cb) +{ + gotPassword(cb); +} + +// App doesn't want to supply login/pwd + +void mail::nntp::LoggedInTask::loginInfoCallbackCancel() +{ + fail("LOGIN cancelled."); +} + +void mail::nntp::LoggedInTask::authinfoUser() +{ + defaultTimeout=myserver->timeoutSetting; + resetTimeout(); + + responseFunc= &mail::nntp::LoggedInTask::doAuthUser; + myserver->socketWrite("AUTHINFO USER " + + myserver->nntpLoginInfo.uid + "\r\n"); +} + +void mail::nntp::LoggedInTask::doAuthUser(const char *msg) +{ + if (atoi(msg) == 381) + { + authinfoPwd(); + return; + } + + if (msg[0] != '2') + { + fail(msg); + return; + } + authCompleted(); +} + +void mail::nntp::LoggedInTask::authinfoPwd() +{ + defaultTimeout=myserver->timeoutSetting; + resetTimeout(); + + responseFunc= &mail::nntp::LoggedInTask::doAuthPwd; + myserver->socketWrite("AUTHINFO PASS " + + myserver->nntpLoginInfo.pwd + "\r\n"); +} + + +void mail::nntp::LoggedInTask::doAuthPwd(const char *resp) +{ + if (resp[0] != '2') + { + fail(resp); + return; + } + authCompleted(); +} + +void mail::nntp::LoggedInTask::authCompleted() +{ + responseFunc= &mail::nntp::LoggedInTask::doModeReader2; + + myserver->socketWrite("MODE READER\r\n"); +} + +void mail::nntp::LoggedInTask::doModeReader2(const char *message) +{ + loginCompleted(); +} + +// Some additional cleanup before calling superclass's loggedIn() + +void mail::nntp::LoggedInTask::loginCompleted() +{ + myserver->serverGroup=""; + loginCompleted2(); +} + +void mail::nntp::LoggedInTask::loginCompleted2() +{ + responseFunc= &mail::nntp::LoggedInTask::doFwdResponse; + myserver->savedLoginInfo.loginCallbackFunc=NULL; + loggingIn=false; + loggedIn(); +} diff --git a/libmail/nntplogin.H b/libmail/nntplogin.H new file mode 100644 index 0000000..612048d --- /dev/null +++ b/libmail/nntplogin.H @@ -0,0 +1,66 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_nntplogin_H +#define libmail_nntplogin_H + +#include "libmail_config.h" + +#include "nntp.H" +#include "logininfo.H" + +LIBMAIL_START + +// +// Superclass of all NNTP tasks that must have a logged in connection +// + +class mail::nntp::LoggedInTask : public mail::nntp::Task, + private loginInfo::callbackTarget { + + void (mail::nntp::LoggedInTask::*responseFunc)(const char *); + + bool loggingIn; + +public: + LoggedInTask(callback *callbackArg, nntp &myserverArg); + ~LoggedInTask(); + + void serverResponse(const char *message); + void installedTask(); + + virtual void loggedIn()=0; + virtual void processLine(const char *message)=0; + + void fail(std::string); +private: + void doFwdResponse(const char *); + + void doGreeting(const char *); + + void authinfoUser(); + void authinfoPwd(); + + void authCompleted(); + + void doAuthUser(const char *); + void doAuthPwd(const char *); + void doModeReader1(const char *); + void doModeReader2(const char *); + + void gotPassword(std::string); + + void loginInfoCallback(std::string); + void loginInfoCallbackCancel(); + + + void doTryAgain(const char *); + + void loginCompleted(); + void loginCompleted2(); +}; + +LIBMAIL_END +#endif diff --git a/libmail/nntplogin2.C b/libmail/nntplogin2.C new file mode 100644 index 0000000..0456e89 --- /dev/null +++ b/libmail/nntplogin2.C @@ -0,0 +1,102 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "nntplogin2.H" +#include <cstring> + +#include <errno.h> + +using namespace std; + +// +// Initial login +// + +mail::nntp::LoginTask::LoginTask(callback *callbackArg, nntp &myserverArg) + : LoggedInTask(callbackArg, myserverArg) +{ +} + +mail::nntp::LoginTask::~LoginTask() +{ +} + +void mail::nntp::LoginTask::loggedIn() +{ + ifstream i(myserver->newsrcFilename.c_str()); + + if (i.is_open()) + { + processListStatus("500 Dummy."); + return; + } + + newNewsrcFilename=myserver->newsrcFilename + ".tmp"; + + newNewsrc.open(newNewsrcFilename.c_str()); + + if (!newNewsrc.is_open()) + { + fail(strerror(errno)); + return; + } + + response_func= &mail::nntp::LoginTask::processListStatus; + myserver->socketWrite("LIST SUBSCRIPTIONS\r\n"); +} + +void mail::nntp::LoginTask::processLine(const char *message) +{ + (this->*response_func)(message); +} + +void mail::nntp::LoginTask::processListStatus(const char *message) +{ + if (message[0] != '2') + { + newNewsrc.close(); + if (newNewsrcFilename.size() > 0) + unlink(newNewsrcFilename.c_str()); + + if (atoi(message) == 480 || + atoi(message) == 381) + fail(message); + else + success("Connected to server."); + return; + } + + response_func= &mail::nntp::LoginTask::processSubscription; +} + +void mail::nntp::LoginTask::processSubscription(const char *message) +{ + if (strcmp(message, ".")) + { + newNewsrc << message << ":" << endl; + return; + } + + newNewsrc.close(); + if (newNewsrc.bad() || newNewsrc.fail() || + rename(newNewsrcFilename.c_str(), + myserver->newsrcFilename.c_str()) < 0) + { + string msg=strerror(errno); + fail(msg); + return; + } + + success("Ok."); +} + +void mail::nntp::LoginTask::fail(string msg) +{ + // Since we never officially logged in, we can't officially disconnect. + + myserver->disconnectCallback=NULL; + + LoggedInTask::fail(msg); +} diff --git a/libmail/nntplogin2.H b/libmail/nntplogin2.H new file mode 100644 index 0000000..f5fe457 --- /dev/null +++ b/libmail/nntplogin2.H @@ -0,0 +1,42 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_nntplogin2_H +#define libmail_nntplogin2_H + +#include "libmail_config.h" +#include <fstream> + +#include "nntplogin.H" + +LIBMAIL_START + +// +// The formal news server login +// + +class mail::nntp::LoginTask : public mail::nntp::LoggedInTask { + + void (mail::nntp::LoginTask::*response_func)(const char *); + + std::ofstream newNewsrc; + std::string newNewsrcFilename; + +public: + LoginTask(callback *callbackArg, nntp &myserverArg); + ~LoginTask(); + + void loggedIn(); + void processLine(const char *message); + void fail(std::string); + +private: + void processListStatus(const char *); + void processSubscription(const char *); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/nntplogout.C b/libmail/nntplogout.C new file mode 100644 index 0000000..474f383 --- /dev/null +++ b/libmail/nntplogout.C @@ -0,0 +1,81 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "nntplogout.H" + +mail::nntp::LogoutTask::LogoutTask(callback *callbackArg, nntp &myserverArg, + bool inactivityTimeoutArg) + : Task(callbackArg, myserverArg), + inactivityTimeout(inactivityTimeoutArg), + goodDisconnect(false) +{ +} + +mail::nntp::LogoutTask::~LogoutTask() +{ +} + +void mail::nntp::LogoutTask::installedTask() +{ + if (myserver->getfd() < 0) + { + success("Ok."); + return; + } + + myserver->socketWrite("QUIT\r\n"); +} + +void mail::nntp::LogoutTask::done() +{ + if (inactivityTimeout) + { + Task::done(); + return; + } + + callback::disconnect *d=myserver->disconnectCallback; + + myserver->disconnectCallback=NULL; + + Task::done(); + if (d) + d->disconnected(""); +} + +void mail::nntp::LogoutTask::serverResponse(const char *msg) +{ + goodDisconnect=true; + + if (!myserver->socketEndEncryption()) + { + myserver->socketDisconnect(); + success("Ok"); + } +} + +void mail::nntp::LogoutTask::disconnected(const char *reason) +{ + if (goodDisconnect) + reason=""; + + if (inactivityTimeout) + { + success("Ok."); + return; + } + + callback::disconnect *d=myserver->disconnectCallback; + + myserver->disconnectCallback=NULL; + + success("Ok."); + if (d) + d->disconnected(reason); +} + +void mail::nntp::LogoutTask::emptyQueue() +{ +} diff --git a/libmail/nntplogout.H b/libmail/nntplogout.H new file mode 100644 index 0000000..db01b9a --- /dev/null +++ b/libmail/nntplogout.H @@ -0,0 +1,40 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_nntplogout_H +#define libmail_nntplogout_H + +#include "libmail_config.h" + +#include "nntp.H" + +LIBMAIL_START + +// +// Formally log out. +// + +class mail::nntp::LogoutTask : public mail::nntp::Task { + + bool inactivityTimeout; + bool goodDisconnect; +public: + LogoutTask(callback *callbackArg, nntp &myserverArg, + bool inactivityTimeoutArg); + ~LogoutTask(); + + void done(); + void installedTask(); + void serverResponse(const char *); + + void disconnected(const char *reason); + + void emptyQueue(); // Override it - do not set inactivity timeout + +}; + +LIBMAIL_END + +#endif diff --git a/libmail/nntpnewsrc.C b/libmail/nntpnewsrc.C new file mode 100644 index 0000000..a68f620 --- /dev/null +++ b/libmail/nntpnewsrc.C @@ -0,0 +1,215 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ + +#include "nntpnewsrc.H" +#include <sstream> + +using namespace std; + +mail::nntp::newsrc::newsrc() : subscribed(true) +{ +} + +mail::nntp::newsrc::~newsrc() +{ +} + +mail::nntp::newsrc::newsrc(string s) : subscribed(true) +{ + if (s.size() == 0 || s[0] == '#') + return; + + string::iterator b=s.begin(), e=s.end(); + + while (b != e && !unicode_isspace((unsigned char)*b)) + { + if (*b == ':') + { + newsgroupname=string(s.begin(), b); + break; + } + + if (*b == '!') + { + subscribed=false; + newsgroupname=string(s.begin(), b); + break; + } + b++; + } + + while (b != e) + { + if (unicode_isspace((unsigned char)*b) || *b == ',') + { + b++; + continue; + } + + string::iterator p=b; + + while (b != e) + { + if (unicode_isspace((unsigned char)*b) || *b == ',') + break; + b++; + } + + string range=string(p, b); + + msgnum_t firstMsg=0, lastMsg=0; + char dummy; + + istringstream i(range); + + i >> firstMsg >> dummy >> lastMsg; + + if (firstMsg > 0) + { + if (dummy == '-' && lastMsg >= firstMsg) + ; + else + lastMsg=firstMsg; + + if (msglist.size() > 0) + { + msgnum_t ending=msglist.end()[-1].second; + + if (ending+1 == firstMsg) + { + msglist.end()[-1].second=lastMsg; + continue; + } + + if (ending >= firstMsg) + continue; // Error -- ignore + } + + msglist.push_back( make_pair(firstMsg, lastMsg)); + } + } +} + +mail::nntp::newsrc::operator string() const +{ + ostringstream o; + + o << newsgroupname << (subscribed ? ":":"!"); + + string sep=" "; + + vector< pair<msgnum_t, msgnum_t> >::const_iterator b=msglist.begin(), + e=msglist.end(); + + while (b != e) + { + o << sep; + sep=","; + o << b->first; + + if (b->second > b->first) + o << "-" << b->second; + b++; + } + + return o.str(); +} + +// Mark msg as read + +void mail::nntp::newsrc::read(msgnum_t m) +{ + vector< pair<msgnum_t, msgnum_t> >::iterator b=msglist.begin(), + e=msglist.end(); + + // We typically do stuff at the end of the list + + while (b != e) + { + --e; + + if (e->first > m) + continue; + + if (m <= e->second) + return; // Already marked read + + bool growThis= e->second + 1 == m; // Grow this entry + + bool growNext= e != msglist.end() && m + 1 == e[1].first; + + if (growThis) + { + if (growNext) // Merge two ranges + { + e->second=e[1].second; + msglist.erase(e+1); + return; + } + + ++e->second; + return; + } + if (growNext) + { + e[1].first--; + return; + } + + msglist.insert(e+1, make_pair(m, m)); + return; + } + + if (msglist.size() > 0 && m + 1 && msglist[0].first) + { + msglist[0].first--; + return; + } + + msglist.insert(msglist.begin(), make_pair(m, m)); +} + +void mail::nntp::newsrc::unread(msgnum_t m) +{ + vector< pair<msgnum_t, msgnum_t> >::iterator b=msglist.begin(), + e=msglist.end(); + + while (b != e) + { + --e; + + if (e->second < m) + break; + + if (e->first > m) + continue; + + + if (e->first == e->second) // Delete this range + { + msglist.erase(e); + break; + } + + if (e->first == m) + { + ++e->first; + break; + } + + if (e->second == m) + { + --e->second; + break; + } + + // Split + + msglist.insert(e+1, make_pair(m+1, e->second)); + e->second=m-1; + break; + } +} diff --git a/libmail/nntpnewsrc.H b/libmail/nntpnewsrc.H new file mode 100644 index 0000000..d19534d --- /dev/null +++ b/libmail/nntpnewsrc.H @@ -0,0 +1,43 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_nntpnewsrc_H +#define libmail_nntpnewsrc_H + +#include "libmail_config.h" +#include "nntp.H" +#include <string> +#include <vector> + +LIBMAIL_START + +///////////////////////////////////////////////////////////////////////////// +// +// The objects that represents lines in a newsrc file + +class mail::nntp::newsrc { + +public: + std::string newsgroupname; + bool subscribed; + + std::vector< std::pair<msgnum_t, msgnum_t> > msglist; + // A list of read messages, in strict ascending order. + + newsrc(); + ~newsrc(); + + newsrc(std::string); // Construct from a line of text in newsrc + + operator std::string() const; + + + void read(msgnum_t); + void unread(msgnum_t); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/nntppost.C b/libmail/nntppost.C new file mode 100644 index 0000000..88fbc10 --- /dev/null +++ b/libmail/nntppost.C @@ -0,0 +1,157 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "nntppost.H" +#include <stdio.h> +#include <sys/stat.h> +#include <errno.h> +#include <cstring> + +using namespace std; + +mail::nntp::PostTask::PostTask(callback *callbackArg, nntp &myserverArg, + FILE *msgArg) + : LoggedInTask(callbackArg, myserverArg), msg(msgArg), + myPumper(NULL) +{ +} + +mail::nntp::PostTask::~PostTask() +{ + if (myPumper) + { + myPumper->writebuffer += "\r\n.\r\n"; + myPumper->me=NULL; + } + if (msg) + fclose(msg); +} + +void mail::nntp::PostTask::loggedIn() +{ + struct stat stat_buf; + + if (fseek(msg, 0L, SEEK_SET) < 0 || fstat(fileno(msg), &stat_buf) < 0) + { + fail(strerror(errno)); + return; + } + + tot_count=stat_buf.st_size; + byte_count=0; + response_func= &mail::nntp::PostTask::doPostStatus; + myserver->socketWrite("POST\r\n"); +} + +void mail::nntp::PostTask::processLine(const char *message) +{ + (this->*response_func)(message); +} + +mail::nntp::PostTask::pump::pump(PostTask *p) : newLine(true), me(p) +{ +} + +mail::nntp::PostTask::pump::~pump() +{ + if (me) + me->myPumper=NULL; +} + +bool mail::nntp::PostTask::pump::fillWriteBuffer() +{ + if (!me) + return false; + + char buffer[BUFSIZ]; + + int n=fread(buffer, 1, sizeof(buffer), me->msg); + + if (n <= 0) + { + if (!newLine) + writebuffer += "\r\n"; + writebuffer += ".\r\n"; + me->myPumper=NULL; + me=NULL; + return true; + } + + // Copy the read chunk into the writebuffer, convert NLs to CRNLs, + // and dot-stuffing. + + char *b=buffer, *e=b + n, *c=b; + + while (b != e) + { + if (newLine && *b == '.') // Leading dot, double it. + { + writebuffer.insert(writebuffer.end(), c, b+1); + c=b; // Net effect is the dot doubled. + } + + if ((newLine= *b == '\n') != 0) + { + if (c != b) + writebuffer.insert(writebuffer.end(), c, b); + writebuffer += "\r"; + c=b; + } + b++; + } + + if (c != b) + writebuffer.insert(writebuffer.end(), c, b); + + me->byte_count += n; + + if (me->tot_count < me->byte_count) + me->tot_count=me->byte_count; + + me->callbackPtr->reportProgress(me->byte_count, me->tot_count, 0, 1); + return true; +} + +void mail::nntp::PostTask::doPostStatus(const char *resp) +{ + if (resp[0] != '3') + { + fail(resp); + return; + } + + response_func= &mail::nntp::PostTask::doPost; + pump *p=new pump(this); + + if (p) + try { + myserver->socketWrite(p); + myPumper=p; + return; + } catch (...) { + delete p; + } + + myserver->socketWrite(".\r\n"); +} + +void mail::nntp::PostTask::doPost(const char *msg) +{ + if (myPumper) + { + myPumper->writebuffer += "\r\n.\r\n"; + myPumper->me=NULL; + } + myPumper=NULL; + + switch (msg[0]) { + case '2': + case '1': + success(msg); + break; + default: + fail(msg); + } +} diff --git a/libmail/nntppost.H b/libmail/nntppost.H new file mode 100644 index 0000000..17b9f43 --- /dev/null +++ b/libmail/nntppost.H @@ -0,0 +1,59 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_nntppost_H +#define libmail_nntppost_H + +#include "libmail_config.h" + +#include "nntp.H" +#include "nntplogin.H" + +#include <stdio.h> + +LIBMAIL_START + +// +// POST a mesasge +// + +class mail::nntp::PostTask : public mail::nntp::LoggedInTask { + + void (mail::nntp::PostTask::*response_func)(const char *); + +public: + FILE *msg; +private: + unsigned long tot_count, byte_count; + + class pump : public WriteBuffer { + + bool newLine; + public: + PostTask *me; + + pump(PostTask *); + ~pump(); + bool fillWriteBuffer(); + }; + + pump *myPumper; +public: + friend class pump; + + PostTask(callback *callbackArg, nntp &myserverArg, + FILE *msgArg); + ~PostTask(); + + virtual void loggedIn(); + virtual void processLine(const char *message); + +private: + void doPostStatus(const char *); + void doPost(const char *); +}; + +LIBMAIL_END +#endif diff --git a/libmail/nntpxover.C b/libmail/nntpxover.C new file mode 100644 index 0000000..710df09 --- /dev/null +++ b/libmail/nntpxover.C @@ -0,0 +1,255 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ + +#include "nntpxover.H" +#include "generic.H" +#include "envelope.H" + +#include <algorithm> +#include <sstream> + +using namespace std; + +mail::nntp::XoverTask::XoverTask(callback::message *callbackArg, + nntp &myserverArg, + string groupNameArg, + const vector<size_t> &messages, + MessageAttributes attributesArg) + : GroupTask(callbackArg, myserverArg, groupNameArg), + attributes(attributesArg), + xoverCallback( *callbackArg ) +{ + vector<size_t> cpy=messages; + + sort(cpy.begin(), cpy.end()); + + vector<size_t>::iterator b=cpy.begin(), e=cpy.end(); + + size_t n=myserverArg.getFolderIndexSize(); + + while (b != e) + { + vector<size_t>::iterator c=b; + + ++c; + + if (c == e || *c != *b) + { + if (*b >= n) + break; + + ostringstream o; + + o << myserverArg.index[*b].msgNum; + + msgUids.push_back(make_pair(*b, o.str())); + } + b=c; + } +} + +mail::nntp::XoverTask::~XoverTask() +{ +} + +void mail::nntp::XoverTask::selectedGroup(msgnum_t estimatedCount, + msgnum_t loArticleCount, + msgnum_t hiArticleCount) +{ + nextUid=msgUids.begin(); + doNextXoverRange(); +} + +void mail::nntp::XoverTask::doNextXoverRange() +{ + // Issue an XOVER for the next range of messages. + + while (nextUid != msgUids.end()) + { + // Skip msgs that no longer exist + + if (!myserver->fixGenericMessageNumber( nextUid->second, + nextUid->first)) + { + nextUid++; + continue; + } + + // Grab a consecutive range of message #s + + prevUid=nextUid; + + do + { + ++nextUid; + } while (nextUid != msgUids.end() && + myserver->fixGenericMessageNumber( nextUid->second, + nextUid->first) && + + myserver->index[nextUid[-1].first].msgNum + 1 == + myserver->index[nextUid->first].msgNum); + + response_func=&mail::nntp::XoverTask::processXoverStatus; + + { + istringstream i(prevUid->second); + + i >> firstMsgNum; + + if (i.bad() || i.fail()) + continue; + } + + { + istringstream i(nextUid[-1].second); + + i >> lastMsgNum; + + if (i.bad() || i.fail()) + continue; + } + + ostringstream o; + + o << "XOVER " << prevUid->second << "-" + << nextUid[-1].second << "\r\n"; + + myserver->socketWrite(o.str()); + return; + } + + // Ok, we've done our part. Let the generics do theirs. + + vector<size_t> msgNums; + + msgNums.reserve(msgUids.size()); + + vector< pair<size_t, string> >::iterator b=msgUids.begin(), + e=msgUids.end(); + + while (b != e) + { + if (myserver->fixGenericMessageNumber(b->second, b->first)) + msgNums.push_back(b->first); + b++; + } + + attributes &= ~(MESSAGESIZE | ENVELOPE | ARRIVALDATE); + + if (attributes && callbackPtr) + { + myserver->genericAttributes(myserver, myserver, msgNums, + attributes, xoverCallback); + callbackPtr=NULL; + } + + success("Ok"); +} + +void mail::nntp::XoverTask::processGroup(const char *msg) +{ + (this->*response_func)(msg); +} + +void mail::nntp::XoverTask::processXoverStatus(const char *msg) +{ + if (*msg != '2') + { + fail(msg); + return; + } + response_func=&mail::nntp::XoverTask::processXover; +} + +void mail::nntp::XoverTask::processXover(const char *msg) +{ + if (strcmp(msg, ".") == 0) + { + doNextXoverRange(); + return; + } + + string xover=msg; + + string fields[8]; + + string::iterator b=xover.begin(), e=xover.end(); + + size_t i; + + for (i=0; i<8; i++) + { + string::iterator c=b; + + while (c != e) + { + if (*c == '\t') + break; + c++; + } + + fields[i]=string(b,c); + + if (c != e) + c++; + b=c; + } + + msgnum_t n=0; + msgnum_t byte_count=0; + msgnum_t line_count=0; + + { + istringstream i(fields[0]); + + i >> n; + + if (i.bad() || i.fail()) + return; + } + + { + istringstream i(fields[6]); + + i >> byte_count; + + if (i.bad() || i.fail()) + byte_count=0; + } + + { + istringstream i(fields[7]); + + i >> line_count; + + if (i.bad() || i.fail()) + line_count=0; + } + + if (n < firstMsgNum || n > lastMsgNum || !callbackPtr) + return; + + mail::envelope env; + + mail::generic::genericBuildEnvelope("Subject", fields[1], env); + mail::generic::genericBuildEnvelope("From", fields[2], env); + mail::generic::genericBuildEnvelope("Date", fields[3], env); + mail::generic::genericBuildEnvelope("Message-ID", fields[4], env); + mail::generic::genericBuildEnvelope("References", fields[5], env); + // Not used, for now... + + size_t msgNum= n - firstMsgNum + prevUid->first; + + if (attributes & MESSAGESIZE) + xoverCallback.messageSizeCallback(msgNum, byte_count); + + if (attributes & ARRIVALDATE) + xoverCallback.messageArrivalDateCallback(msgNum, + env.date); + + if (attributes & ENVELOPE) + xoverCallback.messageEnvelopeCallback(msgNum, env); +} diff --git a/libmail/nntpxover.H b/libmail/nntpxover.H new file mode 100644 index 0000000..6e26389 --- /dev/null +++ b/libmail/nntpxover.H @@ -0,0 +1,62 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_nntpxover_H +#define libmail_nntpxover_H + +#include "libmail_config.h" +#include <fstream> + +#include "nntp.H" +#include "nntpgroup.H" + +#include <set> +#include <string> + +LIBMAIL_START + +// +// XOVER-based implementation of readMessageAttributes() for +// MESSAGESIZE, ENVELOPE, and DATE +// + + +class mail::nntp::XoverTask : public mail::nntp::GroupTask { + + void (mail::nntp::XoverTask::*response_func)(const char *); + + std::vector< std::pair<size_t, std::string> > msgUids; + std::vector< std::pair<size_t, std::string> >::iterator nextUid, + prevUid; + + msgnum_t firstMsgNum, lastMsgNum; + + MessageAttributes attributes; + + callback::message &xoverCallback; + +public: + + XoverTask(callback::message *callbackArg, nntp &myserverArg, + std::string groupNameArg, + const std::vector<size_t> &messages, + MessageAttributes attributes); + + ~XoverTask(); + + void selectedGroup(msgnum_t estimatedCount, + msgnum_t loArticleCount, + msgnum_t hiArticleCount); + void processGroup(const char *); + +private: + void doNextXoverRange(); + void processXoverStatus(const char *); + void processXover(const char *); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/nntpxpat.C b/libmail/nntpxpat.C new file mode 100644 index 0000000..532cf5e --- /dev/null +++ b/libmail/nntpxpat.C @@ -0,0 +1,231 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "nntpxpat.H" +#include <sstream> + +using namespace std; + +mail::nntp::XpatTaskCallback::XpatTaskCallback(mail::searchCallback *cb) + : realCallback(cb) +{ +} + +mail::nntp::XpatTaskCallback::~XpatTaskCallback() +{ +} + +void mail::nntp::XpatTaskCallback::success(std::string message) +{ + // Should NOT happen + + fail(message); +} + +void mail::nntp::XpatTaskCallback::fail(std::string message) +{ + mail::searchCallback *cb=realCallback; + + realCallback=NULL; + + delete this; + + cb->fail(message); +} + +void mail::nntp::XpatTaskCallback::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal + ) +{ + realCallback->reportProgress(bytesCompleted, bytesEstimatedTotal, + messagesCompleted, + messagesEstimatedTotal); +} + + +mail::nntp::XpatTask::XpatTask(XpatTaskCallback *cbArg, + nntp &serverArg, string groupName, + string hdrArg, + string srchArg, bool searchNotArg, + searchParams::Scope searchScopeArg, + size_t rangeLoArg, size_t rangeHiArg) + : GroupTask(cbArg, serverArg, groupName), + cb(cbArg), + hdr(hdrArg), + srch(srchArg), + searchNot(searchNotArg), + searchScope(searchScopeArg), + rangeLo(rangeLoArg), + rangeHi(rangeHiArg) +{ +} + +mail::nntp::XpatTask::~XpatTask() +{ + if (cb) + delete cb; +} + +void mail::nntp::XpatTask::selectedGroup(msgnum_t estimatedCount, + msgnum_t loArticleCount, + msgnum_t hiArticleCount) +{ + response_func= &mail::nntp::XpatTask::processStatusResponse; + + vector<mail::nntp::nntpMessageInfo>::iterator b, e; + + b=myserver->index.begin(); + e=myserver->index.end(); + + while (b != e) + { + b->msgFlag &= ~IDX_SEARCH; + + ++b; + } + + lastIdxMsgNum=myserver->index.begin(); + ostringstream o; + + o << "XPAT " << hdr << " " << + (myserver->index.size() > 0 ? + myserver->index[0].msgNum:1) << "- *" << srch << "*\r\n"; + + myserver->socketWrite(o.str()); + +} + +void mail::nntp::XpatTask::processGroup(const char *line) +{ + (this->*response_func)(line); +} + +void mail::nntp::XpatTask::processStatusResponse(const char *line) +{ + if (line[0] != '2') + { + done(line); + return; + } + response_func= &mail::nntp::XpatTask::processXpatResponse; +} + +void mail::nntp::XpatTask::processXpatResponse(const char *l) +{ + if (*l == '.') + done("OK"); + + msgnum_t n; + + istringstream i(l); + + i >> n; + + if (i.fail() || myserver->index.size() == 0) + return; + + if (lastIdxMsgNum == myserver->index.end()) + --lastIdxMsgNum; + + for (;;) + { + if (lastIdxMsgNum->msgNum > n) + { + if (lastIdxMsgNum == myserver->index.begin()) + break; + --lastIdxMsgNum; + if (lastIdxMsgNum->msgNum < n) + break; + continue; + } + else if (lastIdxMsgNum->msgNum < n) + { + if (++lastIdxMsgNum == myserver->index.end()) + break; + if (lastIdxMsgNum->msgNum > n) + break; + } + else + { + myserver->index[lastIdxMsgNum + - myserver->index.begin()].msgFlag + |= IDX_SEARCH; + break; + } + } +} + +void mail::nntp::XpatTask::done(const char *l) +{ + unsigned char Xor=0; + unsigned char Ror=0; + + if (searchNot) + Xor=IDX_SEARCH; + + switch (searchScope) { + case searchParams::search_all: + Ror=IDX_SEARCH; + break; + + case searchParams::search_unmarked: + Xor |= IDX_FLAGGED; + break; + + case searchParams::search_marked: + break; + + case searchParams::search_range: + Ror=IDX_FLAGGED; + break; + } + + vector<size_t> searchResults; + + vector<nntpMessageInfo>::iterator bb,b, e; + + bb=b=myserver->index.begin(); + e=myserver->index.end(); + + if (searchScope == searchParams::search_range) + { + if (rangeHi > myserver->index.size()) + rangeHi=myserver->index.size(); + + if (rangeLo > rangeHi) + rangeLo=rangeHi; + + e= b + rangeHi; + b += rangeLo; + } + + while (b != e) + { + unsigned char f= (b->msgFlag ^ Xor) | Ror; + + if ((f & IDX_FLAGGED) && (f & IDX_SEARCH)) + searchResults.push_back(b - bb); + + ++b; + + } + + searchCallback *cbPat=cb->realCallback; + + delete cb; + cb=NULL; + callbackPtr=NULL; + + try { + Task::done(); + } catch (...) { + cbPat->success(searchResults); + throw; + } + cbPat->success(searchResults); +} diff --git a/libmail/nntpxpat.H b/libmail/nntpxpat.H new file mode 100644 index 0000000..9c5be3c --- /dev/null +++ b/libmail/nntpxpat.H @@ -0,0 +1,77 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_nntpxpat_H +#define libmail_nntpxpat_H + +#include "libmail_config.h" + +#include "nntp.H" +#include "nntpgroup.H" +#include "search.H" + +#include <string> + +LIBMAIL_START + +// +// nntp::Task wants a mail::callback, but we have a mail::search::callback +// + +class nntp::XpatTaskCallback : public mail::callback { +public: + searchCallback *realCallback; + + XpatTaskCallback(searchCallback *); + ~XpatTaskCallback(); + + void success(std::string message); + void fail(std::string message); + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); +}; + +class mail::nntp::XpatTask : public mail::nntp::GroupTask { + + XpatTaskCallback *cb; + + void (mail::nntp::XpatTask::*response_func)(const char *); + + std::string hdr; + std::string srch; + bool searchNot; + + searchParams::Scope searchScope; + size_t rangeLo, rangeHi; + +public: + XpatTask(XpatTaskCallback *, nntp &, std::string, + std::string, std::string, bool, searchParams::Scope, + size_t, size_t); + ~XpatTask(); + + void selectedGroup(msgnum_t estimatedCount, + msgnum_t loArticleCount, + msgnum_t hiArticleCount); + void processGroup(const char *); + + +private: + void processStatusResponse(const char *); + + void processXpatResponse(const char *); + + void done(const char *); + + std::vector<mail::nntp::nntpMessageInfo>::iterator lastIdxMsgNum; +}; + +LIBMAIL_END + +#endif diff --git a/libmail/objectmonitor.C b/libmail/objectmonitor.C new file mode 100644 index 0000000..a9de063 --- /dev/null +++ b/libmail/objectmonitor.C @@ -0,0 +1,39 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "objectmonitor.H" + +using namespace std; + +mail::ptrBase::ptrBase() +{ +} + +mail::ptrBase::~ptrBase() +{ +} + +mail::obj::obj() +{ +} + +mail::obj::obj(const mail::obj &o) +{ +} + +mail::obj &mail::obj::operator=(const mail::obj &o) +{ + return *this; +} + +mail::obj::~obj() +{ + set<mail::ptrBase *>::iterator b=objectBaseSet.begin(), + e=objectBaseSet.end(); + + while (b != e) + (*b++)->ptrDestroyed(); +} diff --git a/libmail/objectmonitor.H b/libmail/objectmonitor.H new file mode 100644 index 0000000..f04233f --- /dev/null +++ b/libmail/objectmonitor.H @@ -0,0 +1,107 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_objectmonitor_H +#define libmail_objectmonitor_H + +/////////////////////////////////////////////////////////////////////////// +// +// Helper class that detects when the object references by a ptr is destroyed. +// +// Subclass mail::obj. Declare mail::ptr<T>, where T is mail::obj's +// subclass. After mail::obj is destroyed, mail::ptr<T>::operator T *() +// will return NULL + +#include <set> +#include <cstdio> + +#include "namespace.H" + +LIBMAIL_START + +class ptrBase { +public: + ptrBase(); + virtual ~ptrBase(); + virtual void ptrDestroyed()=0; +}; + +template<class T> class ptr : public ptrBase { + + T *r; + +public: + ptr(T *ptrArg) : r(NULL) + { + if (ptrArg) + ptrArg->objectBaseSet.insert(this); + r=ptrArg; + } + + ptr(const ptr &o) : r(NULL) + { + (*this)=o; + } + + ptr &operator=(const ptr &o) + { + if (o.r == NULL || + o.r->objectBaseSet.count(this) == 0) + { + if (o.r) + o.r->objectBaseSet.insert(this); + + if (r && r->objectBaseSet.count(this) > 0) + r->objectBaseSet.erase(r->objectBaseSet + .find(this)); + } + r=o.r; + + return *this; + } + + ~ptr() + { + if (r && r->objectBaseSet.count(this) > 0) + r->objectBaseSet.erase(r->objectBaseSet.find(this)); + } + + operator T *() const + { + return r; + } + + T * operator->() const + { + return r; + } + + bool isDestroyed() const { return r == 0; } + + void ptrDestroyed() { r=NULL; } + +}; + +// Some convenient macros + +#define MONITOR(T) mail::ptr<T> thisMonitor(this) + +#define DESTROYED() ( thisMonitor.isDestroyed() ) + +class obj { + +public: + std::set<ptrBase *> objectBaseSet; + + obj(); + virtual ~obj(); + + obj(const obj &); + obj &operator=(const obj &); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/poll.C b/libmail/poll.C new file mode 100644 index 0000000..fb3a8d3 --- /dev/null +++ b/libmail/poll.C @@ -0,0 +1,17 @@ +/* +** Copyright 2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "mail.H" +#include "soxwrap/soxwrap.h" +#include <sys/poll.h> + +int mail::account::poll(std::vector<mail::pollfd> &fds, int timeout) +{ + int npfd=fds.size(); + + struct ::pollfd *pfd=npfd == 0 ? NULL: &fds[0]; + + return ::sox_poll(pfd, npfd, timeout); +} diff --git a/libmail/pop3.C b/libmail/pop3.C new file mode 100644 index 0000000..6866821 --- /dev/null +++ b/libmail/pop3.C @@ -0,0 +1,2845 @@ +/* +** Copyright 2002-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "driver.H" +#include "mail.H" +#include "misc.H" +#include "addmessage.H" +#include "pop3.H" +#include "copymessage.H" +#include "search.H" +#include "imaphmac.H" +#include "base64.H" +#include "objectmonitor.H" +#include "libcouriertls.h" +#include "expungelist.H" +#include <sstream> +#include <iomanip> +#include <time.h> +#include <errno.h> +#include <unistd.h> +#include <algorithm> +#include <vector> +#include <signal.h> +#include <fcntl.h> +#include <set> +#include <cstring> + +#include "rfc2045/rfc2045.h" + +using namespace std; + +///////////////////////////////////////////////////////////////////////////// + +LIBMAIL_START + +static bool open_pop3(mail::account *&accountRet, + mail::account::openInfo &oi, + mail::callback &callback, + mail::callback::disconnect &disconnectCallback) +{ + mail::loginInfo pop3LoginInfo; + + if (!mail::loginUrlDecode(oi.url, pop3LoginInfo)) + return false; + + if (pop3LoginInfo.method != "pop3" && + pop3LoginInfo.method != "pop3s") + return false; + + accountRet=new mail::pop3(oi.url, oi.pwd, + oi.certificates, + oi.loginCallbackObj, + callback, + disconnectCallback); + return true; +} + +static bool pop3_remote(string url, bool &flag) +{ + mail::loginInfo pop3LoginInfo; + + if (!mail::loginUrlDecode(url, pop3LoginInfo)) + return false; + + if (pop3LoginInfo.method != "pop3" && + pop3LoginInfo.method != "pop3s") + return false; + + flag=true; + return true; +} + +driver pop3_driver= { &open_pop3, &pop3_remote }; + +LIBMAIL_END + +///////////////////////////////////////////////////////////////////////////// +// +// Most tasks will subclass from LoggedInTask which makes sure we're +// logged in to the POP3 server if we're not currently logged in. +// +// Exported methods: +// +// LoggedInTask(mail::pop3 &server) // Constructor +// +// mail::pop3 *myserver; // Inherited from Task +// +// void loggedIn(); // Logged in to the server, begin doing what you need to do +// void loginFailed(string errmsg); // Log in failed, about to be destroyed +// +// int getTimeout(); // Inherited from Task +// void disconnected(const char *reason); // Inherited from Task +// void serverResponse(const char *message); // Inherited from task +// + +class mail::pop3::LoggedInTask : public mail::pop3::Task, + public mail::callback { + + // Inherited from mail::callback + + void success(string); + void fail(string); + + void installedTask(); + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + +public: + LoggedInTask(mail::callback *callback, mail::pop3 &myserverArg); + ~LoggedInTask(); + + virtual void loggedIn()=0; + virtual void loginFailed(string error)=0; +}; + +mail::pop3::Task::Task(mail::callback *callbackArg, mail::pop3 &myserverArg) + : callbackPtr(callbackArg), myserver(&myserverArg) +{ + time(&defaultTimeout); + defaultTimeout += myserverArg.timeoutSetting; +} + +mail::pop3::Task::~Task() +{ + mail::callback *c=callbackPtr; + + callbackPtr=NULL; + + if (c) + c->fail("POP3 task terminated unexpectedly."); +} + +void mail::pop3::Task::installedTask() +{ + resetTimeout(); +} + +void mail::pop3::Task::resetTimeout() +{ + time(&defaultTimeout); + defaultTimeout += myserver->timeoutSetting; +} + +int mail::pop3::Task::getTimeout() +{ + time_t now; + + time(&now); + + if (now < defaultTimeout) + return defaultTimeout - now; + + string errmsg=myserver->socketDisconnect(); + + if (errmsg.size() == 0) + errmsg="Server timed out."; + + disconnected(errmsg.c_str()); + return 0; +} + +void mail::pop3::Task::disconnected(const char *reason) +{ + if (myserver->tasks.empty() || + (*myserver->tasks.begin()) != this) + kill(getpid(), SIGABRT); + + mail::callback *c=callbackPtr; + + callbackPtr=NULL; + + try { + myserver->tasks.erase(myserver->tasks.begin()); + + if (!myserver->tasks.empty()) + (*myserver->tasks.begin())->disconnected(reason); + delete this; + + if (c) + c->fail(reason); + } catch (...) { + if (c) + c->fail(reason); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +void mail::pop3::Task::done() +{ + mail::callback *c=callbackPtr; + + callbackPtr=NULL; + + if (myserver->tasks.empty() || (*myserver->tasks.begin()) != this) + kill(getpid(), SIGABRT); + + myserver->tasks.erase(myserver->tasks.begin()); + + mail::pop3 *s=myserver; + + bool flag=isLogoutTask(); + + delete this; + + if (!s->tasks.empty()) + (*s->tasks.begin())->installedTask(); + else if (!flag) + time(&s->lastTaskCompleted); + + if (c) + c->fail("POP3 task aborted."); +} + +bool mail::pop3::Task::isLogoutTask() +{ + return false; +} + +bool mail::pop3::Task::willReconnect() +{ + return false; +} + +///////////////////////////////////////////////////////////////////////////// +// +// Log into the server, and create mail::pop3::uidlmap. + +class mail::pop3::LoginTask : public mail::pop3::Task, + public mail::loginInfo::callbackTarget { + + void fail(const char *reason); + + void (LoginTask::*currentHandler)(const char *status); + + void serverResponse(const char *message); + + void installedTask(); + + void greetingHandler(const char *message); + void capaHandler(const char *message); + void stlsHandler(const char *message); + void stlsCapaHandler(const char *message); + + int hmac_index; // Next HMAC method to try. + void hmacHandler(const char *message); + void hmacChallengeHandler(const char *message); + void userHandler(const char *message); + void passHandler(const char *message); + void listHandler(const char *message); + void uidlHandler(const char *message); + + void addCapability(const char *message); + void addStlsCapability(const char *message); + void processExternalLogin(const char *message); + + void processedCapabilities(); + void loggedIn(const char *message); + + void (mail::pop3::LoginTask::*login_callback_handler)(std::string); + + void loginInfoCallback(std::string); + void loginInfoCallbackCancel(); + + void loginCallbackUid(std::string); + void loginCallbackPwd(std::string); + + void stlsCapaDone(); + void nonExternalLogin(); +public: + LoginTask(mail::pop3 &server, mail::callback *callbackArg); + ~LoginTask(); +}; + +mail::pop3::LoginTask::LoginTask(mail::pop3 &server, + mail::callback *callbackArg) + : Task(callbackArg, server) +{ +} + +mail::pop3::LoginTask::~LoginTask() +{ +} + +void mail::pop3::LoginTask::fail(const char *reason) +{ + mail::callback *c=callbackPtr; + + callbackPtr=NULL; + + try { + string errmsg="ERROR: "; + + if (strncmp(reason, "-ERR", 4) == 0) + { + reason += 4; + + while (*reason == ' ') + reason++; + } + + errmsg += reason; + + done(); + + if (c) + c->fail(errmsg.c_str()); + } catch (...) { + if (c) + c->fail("ERROR: An exception occured in mail::pop3"); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +void mail::pop3::LoginTask::serverResponse(const char *message) +{ + if (currentHandler) + (this->*currentHandler)(message); +} + +void mail::pop3::LoginTask::installedTask() +{ + Task::installedTask(); + + if (myserver->getfd() >= 0) + { + loggedIn("+OK Already logged in."); // Already + return; + } + + currentHandler= &LoginTask::greetingHandler; + + myserver->pop3LoginInfo = myserver->savedLoginInfo; + + string errmsg=myserver-> + socketConnect(myserver->pop3LoginInfo, "pop3", "pop3s"); + + if (errmsg.size() > 0) + { + fail(errmsg.c_str()); + return; + } +} + +// First message from the server. + +void mail::pop3::LoginTask::greetingHandler(const char *message) +{ + if (*message != '+') + { + myserver->socketDisconnect(); + fail(message); + return; + } + + myserver->socketWrite("CAPA\r\n"); + currentHandler= &LoginTask::capaHandler; +} + +// CAPA reply. + +void mail::pop3::LoginTask::capaHandler(const char *message) +{ + myserver->capabilities.clear(); + + if (*message == '-') + { + processedCapabilities(); // No capabilities + return; + } + + if (*message != '+') + { + fail(message); + return; + } + currentHandler=&LoginTask::addCapability; +} + +// Capability list. + +void mail::pop3::LoginTask::addCapability(const char *message) +{ + if (strcmp(message, ".") == 0) + { + processedCapabilities(); + return; + } + + string c=message, v=""; + size_t p=c.find(' '); + + if (p != std::string::npos) + { + v=c.substr(p+1); + c=c.substr(0, p); + } + + // Add AUTH=method capability for each SASL listed method. + + if (c == "SASL") + { + while ((p=v.find(' ')) != std::string::npos) + { + c="AUTH=" + v.substr(0, p); + v=v.substr(p+1); + myserver->capabilities.insert(make_pair(c, + string("1"))); + } + c="AUTH=" + v; + v="1"; + } + + if (v.size() == 0) + v="1"; + + myserver->capabilities.insert(make_pair(c, v)); +} + +void mail::pop3::LoginTask::processedCapabilities() +{ +#if HAVE_LIBCOURIERTLS + + if (myserver->pop3LoginInfo.use_ssl + || myserver->pop3LoginInfo.options.count("notls") > 0 + || !myserver->hasCapability("STLS")) + { + stlsCapaDone(); + return; + } + + currentHandler=&LoginTask::stlsHandler; + myserver->socketWrite("STLS\r\n"); +#else + stlsCapaDone(); +#endif +} + +void mail::pop3::LoginTask::stlsHandler(const char *message) +{ + if (*message != '+') + { + fail(message); + return; + } + +#if HAVE_LIBCOURIERTLS + + myserver->pop3LoginInfo.callbackPtr=callbackPtr; + + callbackPtr=NULL; + if (!myserver->socketBeginEncryption(myserver->pop3LoginInfo)) + return; // Can't set callbackPtr to NULL now, because + // this object could be destroyed. + + // If beginEcnryption() succeeded, restore the callback ptr. + callbackPtr=myserver->pop3LoginInfo.callbackPtr; +#endif + currentHandler=&LoginTask::stlsCapaHandler; + myserver->socketWrite("CAPA\r\n"); +} + +void mail::pop3::LoginTask::stlsCapaHandler(const char *message) +{ + myserver->capabilities.clear(); + + if (*message == '-') + { + processedCapabilities(); // No capabilities + return; + } + + if (*message != '+') + { + fail(message); + return; + } + currentHandler=&LoginTask::addStlsCapability; +} + +void mail::pop3::LoginTask::addStlsCapability(const char *message) +{ + if (strcmp(message, ".") == 0) + { + stlsCapaDone(); + return; + } + addCapability(message); +} + +void mail::pop3::LoginTask::stlsCapaDone() +{ + if (!myserver->hasCapability("AUTH=EXTERNAL")) + { + nonExternalLogin(); + return; + } + + currentHandler=&LoginTask::processExternalLogin; + myserver->socketWrite("AUTH EXTERNAL =\r\n"); +} + +void mail::pop3::LoginTask::processExternalLogin(const char *message) +{ + if (*message != '+') + { + nonExternalLogin(); + return; + } + + loggedIn(message); +} + +void mail::pop3::LoginTask::nonExternalLogin() +{ + if (!myserver->pop3LoginInfo.loginCallbackFunc) + { + loginCallbackPwd(myserver->pop3LoginInfo.pwd); + return; + } + + currentHandler=NULL; + if (myserver->pop3LoginInfo.uid.size() > 0) + { + loginCallbackUid(myserver->pop3LoginInfo.uid); + return; + } + + login_callback_handler= &mail::pop3::LoginTask::loginCallbackUid; + currentCallback=myserver->pop3LoginInfo.loginCallbackFunc; + currentCallback->target=this; + currentCallback->getUserid(); +} + +void mail::pop3::LoginTask::loginInfoCallback(std::string cbvalue) +{ + currentCallback=NULL; + (this->*login_callback_handler)(cbvalue); +} + + +void mail::pop3::LoginTask::loginInfoCallbackCancel() +{ + currentCallback=NULL; + fail("Login cancelled."); +} + +void mail::pop3::LoginTask::loginCallbackUid(std::string uid) +{ + myserver->savedLoginInfo.uid= + myserver->pop3LoginInfo.uid=uid; + + if (myserver->pop3LoginInfo.pwd.size() > 0) + { + loginCallbackPwd(myserver->pop3LoginInfo.pwd); + return; + } + + login_callback_handler= &mail::pop3::LoginTask::loginCallbackPwd; + currentCallback=myserver->pop3LoginInfo.loginCallbackFunc; + currentCallback->target=this; + currentCallback->getPassword(); +} + +void mail::pop3::LoginTask::loginCallbackPwd(std::string pwd) +{ + myserver->savedLoginInfo.pwd=myserver->pop3LoginInfo.pwd=pwd; + + hmac_index=0; + hmacHandler("-ERR Login failed"); // Not really +} + +void mail::pop3::LoginTask::hmacHandler(const char *message) +{ + if (*message == '+') + { + loggedIn(message); + return; + } + + for (;;) + { + if (mail::imaphmac::hmac_methods[hmac_index] == NULL || + myserver->pop3LoginInfo.uid.size() == 0) + { + if (myserver->pop3LoginInfo.options.count("cram")) + fail(message); + + if (myserver->pop3LoginInfo.uid.size() == 0) + { + loggedIn("+OK No userid, assuming preauthenticated login."); + return; + } + + currentHandler=&LoginTask::userHandler; + myserver->socketWrite("USER " + + myserver->pop3LoginInfo.uid + + "\r\n"); + return; + } + + if (!myserver->hasCapability(string("AUTH=CRAM-") + + mail::imaphmac + ::hmac_methods[hmac_index] + ->getName())) + { + ++hmac_index; + continue; + } + break; + } + + currentHandler=&LoginTask::hmacChallengeHandler; + myserver->socketWrite(string("AUTH CRAM-") + + mail::imaphmac::hmac_methods[hmac_index] + ->getName() + "\r\n"); +} + +void mail::pop3::LoginTask::hmacChallengeHandler(const char *message) +{ + if (*message != '+') + { + ++hmac_index; + hmacHandler(message); + return; + } + + do + { + ++message; + } while (*message == ' '); + + string s=mail::decodebase64str(message); + + s= (*mail::imaphmac::hmac_methods[hmac_index++]) + (myserver->pop3LoginInfo.pwd, s); + string sHex; + + { + ostringstream hexEncode; + + hexEncode << hex; + + string::iterator b=s.begin(); + string::iterator e=s.end(); + + while (b != e) + hexEncode << setw(2) << setfill('0') + << (int)(unsigned char)*b++; + sHex=hexEncode.str(); + } + + s=mail::encodebase64str(myserver->pop3LoginInfo.uid + " " + sHex); + currentHandler=&LoginTask::hmacHandler; + myserver->socketWrite(s + "\r\n"); +} + +void mail::pop3::LoginTask::userHandler(const char *message) +{ + if (*message != '+') + { + fail(message); + return; + } + + currentHandler=&LoginTask::passHandler; + myserver->socketWrite("PASS " + + myserver->pop3LoginInfo.pwd + "\r\n"); +} + +void mail::pop3::LoginTask::passHandler(const char *message) +{ + if (*message != '+') + { + fail(message); + return; + } + loggedIn(message); +} + +void mail::pop3::LoginTask::loggedIn(const char *message) +{ + myserver->savedLoginInfo.loginCallbackFunc= + myserver->pop3LoginInfo.loginCallbackFunc=NULL; // Got it + currentHandler=&LoginTask::listHandler; + myserver->listMap.clear(); + myserver->socketWrite("LIST\r\n"); +} + +void mail::pop3::LoginTask::listHandler(const char *message) +{ + if (*message == '-') + { + fail(message); + return; + } + + if (*message == '+') + return; // LIST results follow + + if (strcmp(message, ".") == 0) + { + myserver->uidlMap.clear(); + + if (!myserver->ispop3maildrop()) + { + currentHandler=&LoginTask::uidlHandler; + myserver->socketWrite("UIDL\r\n"); + return; + } + + uidlHandler("."); /* Don't do UIDL for pop3 maildrops */ + return; + } + + int messageNum=0; + unsigned long messageSize=0; + + istringstream i(message); + + i >> messageNum; + + i >> messageSize; + + myserver->listMap.insert(make_pair(messageNum, messageSize)); +} + +void mail::pop3::LoginTask::uidlHandler(const char *message) +{ + if (*message == '-') + { + fail("-ERR This POP3 server does not appear to implement the POP3 UIDL command. Upgrade the POP3 server software, or use the POP3 maildrop mode."); + return; + } + + if (*message == '+') + return; // UIDL results follow + + if (strcmp(message, ".") == 0) + { + mail::callback *c=callbackPtr; + + callbackPtr=NULL; + + done(); + + if (c) + { + c->success("Logged in."); + } + return; + } + + int msgNum=0; + + while (*message && isdigit((int)(unsigned char)*message)) + msgNum=msgNum * 10 + (*message++ - '0'); + + while (*message && unicode_isspace((unsigned char)*message)) + message++; + + myserver->uidlMap.insert(make_pair(string(message), msgNum)); +} + + +////////////////////////////////////////////////////////////////////////////// + +mail::pop3::LoggedInTask::LoggedInTask(mail::callback *callback, + mail::pop3 &myserverArg) + : Task(callback, myserverArg) +{ +} + +mail::pop3::LoggedInTask::~LoggedInTask() +{ + mail::callback *c=callbackPtr; + + callbackPtr=NULL; + + if (c) + c->fail("An exception occurred while trying to initialize this task."); +} + +void mail::pop3::LoggedInTask::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + if (callbackPtr) + callbackPtr-> + reportProgress(bytesCompleted, bytesEstimatedTotal, + messagesCompleted, + messagesEstimatedTotal); +} + +void mail::pop3::LoggedInTask::installedTask() +{ + Task::installedTask(); + + if (myserver->getfd() >= 0) // Logged in, yippee!! + { + loggedIn(); + return; + } + + myserver->installTask(new LoginTask( *myserver, this)); + + if ((*myserver->tasks.begin()) != this) + kill(getpid(), SIGABRT); + + myserver->tasks.erase(myserver->tasks.begin()); // myself + + (*myserver->tasks.begin())->installedTask(); + // Guaranteed something's there. +} + +void mail::pop3::LoggedInTask::success(string message) +{ + myserver->installTask(this); // Try again +} + +void mail::pop3::LoggedInTask::fail(string message) +{ + mail::callback *c=callbackPtr; + + callbackPtr=NULL; + + delete this; + + if (c) + c->fail(message); +} + +///////////////////////////////////////////////////////////////////////////// + +class mail::pop3::CheckNewMailTask : public mail::pop3::LoggedInTask { + +public: + bool forceSaveSnapshot; + + class sortNewMail { + + map<string, int> &umap; + public: + sortNewMail(map<string, int> &umapArg); + ~sortNewMail(); + + bool operator()(string a, string b); + }; + + + CheckNewMailTask(mail::pop3 &serverArg, mail::callback &callbackArg); + ~CheckNewMailTask(); + + void loggedIn(); + void loginFailed(string errmsg); + + void serverResponse(const char *message); + + void done(); +private: + void (CheckNewMailTask::*currentHandler)(const char *status); + int next2Download; // When subclass by pop3maildrop, next msg 2 get. + bool willReconnectFlag; + + bool willReconnect(); + + + void doNextDownload(); + mail::addMessage *downloadMsg; + + void retrHandler(const char *); + void retrBodyHandler(const char *); + void doDeleteDownloaded(); + void deleHandler(const char *); + void quitHandler(const char *); + + size_t bytesCompleted; + size_t bytesEstimatedTotal; + + size_t messagesEstimatedTotal; + +}; + +mail::pop3::CheckNewMailTask::CheckNewMailTask(mail::pop3 &serverArg, + mail::callback &callbackArg) + : LoggedInTask(&callbackArg, serverArg), + forceSaveSnapshot(false), willReconnectFlag(false) +{ +} + +mail::pop3::CheckNewMailTask::~CheckNewMailTask() +{ +} + +void mail::pop3::CheckNewMailTask::serverResponse(const char *message) +{ + if (currentHandler) + (this->*currentHandler)(message); +} + +void mail::pop3::CheckNewMailTask::loggedIn() +{ + mail::callback *myCallback=callbackPtr; + + callbackPtr=NULL; + + mail::ptr<mail::pop3> serverPtr= myserver; + + bool changedFolder=myserver->reconcileFolderIndex(); + + if (forceSaveSnapshot) + changedFolder=true; // Explicit check new mail saves snapshot + + // + // Now, save a snapshot. + // + if (!serverPtr.isDestroyed() && serverPtr->folderCallback && + changedFolder) + serverPtr->folderCallback->saveSnapshot("POP3"); + + if (!serverPtr.isDestroyed()) + { + if (serverPtr->ispop3maildrop()) + { + serverPtr->pop3maildropreset(); + + callbackPtr=myCallback; + + next2Download=1; + messagesEstimatedTotal=myserver->listMap.size(); + doNextDownload(); + return; + } + done(); + } + + if (myCallback) + myCallback->success("OK"); +} + +void mail::pop3::CheckNewMailTask::loginFailed(string errmsg) +{ + mail::callback *c=callbackPtr; + + callbackPtr=NULL; + + done(); + + if (c) + c->fail(errmsg); +} + +mail::pop3::CheckNewMailTask::sortNewMail +::sortNewMail(map<string, int> &umapArg) + : umap(umapArg) +{ +} + +mail::pop3::CheckNewMailTask::sortNewMail::~sortNewMail() +{ +} + +bool mail::pop3::CheckNewMailTask::sortNewMail::operator()(string a, + string b) +{ + map<string, int>::iterator ap, bp; + + ap=umap.find(a); + bp=umap.find(b); + + return ((ap == umap.end() ? 0:ap->second) < + (bp == umap.end() ? 0:bp->second)); +} + + +void mail::pop3::CheckNewMailTask::doNextDownload() +{ + std::map<int, unsigned long>::iterator p= + myserver->listMap.find(next2Download); + + bytesCompleted=0; + bytesEstimatedTotal=0; + + if (next2Download > 200 || + // Grab max 200 msgs at a time to prevent out of file descriptors + p == myserver->listMap.end()) + { + string errmsg=myserver->commitDownloadedMsgs(); + + if (errmsg.size() > 0) + { + mail::callback *c=callbackPtr; + callbackPtr=NULL; + done(); + if (c) + c->fail(errmsg.c_str()); + return; + } + if (callbackPtr) + callbackPtr->reportProgress(0, 0, + messagesEstimatedTotal, + messagesEstimatedTotal); + doDeleteDownloaded(); + return; + } + + + downloadMsg=myserver->newDownloadMsg(); + + if (!downloadMsg) + { + mail::callback *c=callbackPtr; + callbackPtr=NULL; + done(); + if (c) + c->fail(strerror(errno)); + return; + } + downloadMsg->messageInfo.unread=true; /* New message */ + + currentHandler=&CheckNewMailTask::retrHandler; + + ostringstream o; + + bytesCompleted=0; + + if ((size_t)next2Download > messagesEstimatedTotal) + messagesEstimatedTotal=next2Download; + + map<int, unsigned long>::iterator s= + myserver->listMap.find(next2Download); + + bytesEstimatedTotal=s == myserver->listMap.end() ? 0: + s->second; + + if (callbackPtr) + callbackPtr->reportProgress(0, + bytesEstimatedTotal, + next2Download-1, + messagesEstimatedTotal); + o << "RETR " << next2Download << "\r\n"; + + myserver->socketWrite(o.str()); + ++next2Download; +} + + +void mail::pop3::CheckNewMailTask::retrHandler(const char *message) +{ + if (*message != '+') + { + mail::callback *c=callbackPtr; + callbackPtr=NULL; + done(); + if (c) + c->fail(message); + return; + } + + currentHandler=&CheckNewMailTask::retrBodyHandler; +} + +void mail::pop3::CheckNewMailTask::retrBodyHandler(const char *message) +{ + if (strcmp(message, ".")) + { + if (*message == '.') + ++message; + + bytesCompleted += strlen(message)+2; + if (bytesCompleted > bytesEstimatedTotal) + bytesCompleted=bytesEstimatedTotal; + if (callbackPtr) + callbackPtr->reportProgress(bytesCompleted, + bytesEstimatedTotal, + next2Download-1, + messagesEstimatedTotal); + downloadMsg->saveMessageContents(string(message)+"\n"); + return; + } + + doNextDownload(); +} + +void mail::pop3::CheckNewMailTask::doDeleteDownloaded() +{ + if (--next2Download <= 0) + { + willReconnectFlag=true; + currentHandler=&CheckNewMailTask::quitHandler; + myserver->socketWrite("QUIT\r\n"); + return; + } + + currentHandler=&CheckNewMailTask::deleHandler; + + ostringstream o; + + o << "DELE " << next2Download << "\r\n"; + myserver->socketWrite(o.str()); +} + +void mail::pop3::CheckNewMailTask::deleHandler(const char *message) +{ + doDeleteDownloaded(); +} + +void mail::pop3::CheckNewMailTask::quitHandler(const char *message) +{ + if (myserver->socketEndEncryption()) + return; // Catch me on the rebound. + + mail::callback *c=callbackPtr; + + callbackPtr=NULL; + + myserver->socketDisconnect(); + myserver->orderlyShutdown=true; + myserver->disconnect(""); + + if (c) + c->success("Ok"); +} + + +bool mail::pop3::CheckNewMailTask::willReconnect() +{ + return willReconnectFlag; +} + +void mail::pop3::CheckNewMailTask::done() +{ + if (!willReconnectFlag) + { + LoggedInTask::done(); + return; + } + + mail::callback *c=callbackPtr; + callbackPtr=NULL; + LoggedInTask::done(); + if (c) + c->success("Ok."); +} + +///////////////////////////////////////////////////////////////////////////// + +class mail::pop3::LogoutTask : public mail::pop3::Task { + + void fail(const char *reason); + + void serverResponse(const char *message); + + void installedTask(); + bool isLogoutTask(); + + mail::callback *disconnectCallback; + + bool willReconnectFlag; + +public: + LogoutTask(mail::pop3 &server, mail::callback *callbackArg, + bool willReconnectFlagArg=false); + ~LogoutTask(); + + bool willReconnect(); +}; + +mail::pop3::LogoutTask::LogoutTask(mail::pop3 &server, + mail::callback *callbackArg, + bool willReconnectFlagArg) + : Task(callbackArg, server), + disconnectCallback(NULL), + willReconnectFlag(willReconnectFlagArg) +{ +} + +bool mail::pop3::LogoutTask::willReconnect() +{ + return willReconnectFlag; +} + +mail::pop3::LogoutTask::~LogoutTask() +{ + myserver->socketDisconnect(); + if (disconnectCallback) + disconnectCallback->success("OK"); +} + +bool mail::pop3::LogoutTask::isLogoutTask() +{ + return true; +} + +void mail::pop3::LogoutTask::installedTask() +{ + Task::installedTask(); + + if (myserver->getfd() < 0) + { + serverResponse("OK"); // Already logged out. + return; + } + + myserver->socketWrite("QUIT\r\n"); +} + +void mail::pop3::LogoutTask::serverResponse(const char *message) +{ + const char *p=strchr(message, ' '); + + if (p) + message=p+1; + + mail::callback *c=callbackPtr; + + callbackPtr=NULL; + + disconnectCallback=c; + + if (myserver->socketEndEncryption()) + return; // Catch me on the rebound. + + myserver->socketDisconnect(); + myserver->orderlyShutdown=true; + myserver->disconnect(""); +} + +///////////////////////////////////////////////////////////////////////////// + +class mail::pop3::ForceCheckNewMailTask : public mail::callback { + + mail::callback *realCallback; + mail::pop3 *server; + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + +public: + ForceCheckNewMailTask(mail::callback *realCallbackArg, + mail::pop3 *serverArg); + ~ForceCheckNewMailTask(); + + void success(string); + void fail(string); +}; + +mail::pop3::ForceCheckNewMailTask +::ForceCheckNewMailTask(mail::callback *realCallbackArg, + mail::pop3 *serverArg) + : realCallback(realCallbackArg), server(serverArg) +{ +} + +mail::pop3::ForceCheckNewMailTask::~ForceCheckNewMailTask() +{ +} + +void mail::pop3::ForceCheckNewMailTask +::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + if (realCallback) + realCallback->reportProgress(bytesCompleted, + bytesEstimatedTotal, + messagesCompleted, + messagesEstimatedTotal); +} + +// +// checkNewMail installed a LogoutTask. LogoutTask's destructor ends up +// calling the logout callback function, which is me. +// LogoutTask is destroyed by LogoutTask::Task::done()'s delete this. +// +// Install the CheckNewMailTask at the beginning of the task queue, so +// Task::done() ends up calling CheckNewMailTask's installedTask. + +void mail::pop3::ForceCheckNewMailTask::success(string message) +{ + CheckNewMailTask *cn=new CheckNewMailTask(*server, *realCallback); + + if (!cn) + realCallback->fail(strerror(errno)); + + try { + cn->forceSaveSnapshot=true; + server->lastTaskCompleted=0; + server->tasks.insert(server->tasks.begin(), cn); + delete this; + } catch (...) { + delete cn; + realCallback->fail(message); + delete this; + } +} + +void mail::pop3::ForceCheckNewMailTask::fail(string errmsg) +{ + success(errmsg); +} + +///////////////////////////////////////////////////////////////////////////// +// +// EXPUNGE - sent all the DELEs, then QUIT out of the folder, then reopen it. + +class mail::pop3::UpdateTask : public mail::pop3::LoggedInTask { + + size_t updateIndex; + +#if 0 + class LogoutCallback : public mail::callback { + mail::ptr<mail::pop3> myserver; + mail::callback *callback; + public: + LogoutCallback(mail::pop3 *serverArg, + mail::callback *callbackArg); + ~LogoutCallback(); + + void success(string); + void fail(string); + }; +#endif + +public: + UpdateTask(mail::pop3 &serverArg, mail::callback &callbackArg); + ~UpdateTask(); + + void loggedIn(); + void loginFailed(string errmsg); + + void serverResponse(const char *message); +}; + +mail::pop3::UpdateTask::UpdateTask(mail::pop3 &serverArg, + mail::callback &callbackArg) + : LoggedInTask(&callbackArg, serverArg), + updateIndex(0) +{ +} + +mail::pop3::UpdateTask::~UpdateTask() +{ +} + +void mail::pop3::UpdateTask::loggedIn() +{ + serverResponse("+OK"); +} + +void mail::pop3::UpdateTask::loginFailed(string errmsg) +{ + mail::callback *c=callbackPtr; + + callbackPtr=NULL; + + done(); + + if (c) + c->fail(errmsg); +} + +void mail::pop3::UpdateTask::serverResponse(const char *message) +{ + if (*message != '+') + { + loginFailed(message); + return; + } + + // Check for deleted messages. + + while (updateIndex < myserver->currentFolderIndex.size()) + { + string uid; + + if (myserver->currentFolderIndex[updateIndex].deleted && + myserver->uidlMap.count(uid=myserver->currentFolderIndex + [updateIndex].uid) > 0) + { + int msgNum=myserver->uidlMap.find(uid)->second; + + string buffer; + { + ostringstream o; + + o << msgNum; + buffer=o.str(); + } + + ++updateIndex; + myserver->socketWrite(string("DELE ") + buffer + + "\r\n"); + return; + } + ++updateIndex; + } + + // Do the usual logout logic, which will take care of this. + + ForceCheckNewMailTask *l= + new ForceCheckNewMailTask(callbackPtr, myserver); + + if (!l) + { + loginFailed("Out of memory."); + return; + } + + callbackPtr=NULL; + + if ( (*myserver->tasks.begin()) != this) + kill(getpid(), SIGABRT); + + try { + LogoutTask *lo=new LogoutTask( *myserver, l, true); + + if (!lo) + strerror(errno); + + try { + myserver->tasks.insert(++myserver->tasks.begin(), + lo); + } catch (...) { + delete lo; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + } catch (...) { + delete l; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + done(); +} + +#if 0 +mail::pop3::UpdateTask::LogoutCallback::LogoutCallback(mail::pop3 *serverArg, + mail::callback + *callbackArg) + : myserver(serverArg), callback(callbackArg) +{ +} + +mail::pop3::UpdateTask::LogoutCallback::~LogoutCallback() +{ +} + +void mail::pop3::UpdateTask::LogoutCallback::success(string message) +{ + mail::callback *c=callback; + mail::pop3 *s=myserver; + + callback=NULL; + delete this; + + if (!s) + c->success(message); + else try { + s->checkNewMail(*c); // Immediately check for new mail. + } catch (...) { + c->success(message); + } +} + +void mail::pop3::UpdateTask::LogoutCallback::fail(string message) +{ + success(message); // I'm an optimist. +} +#endif + +///////////////////////////////////////////////////////////////////////////// +// +// Generic message read. + +class mail::pop3::ReadMessageTask : public mail::pop3::LoggedInTask { + + string uid; + size_t messageNum; + bool peek; + mail::readMode readType; + + bool seenHeaders; + string currentHeader; + bool expectingStatus; + + size_t expected_cnt; + size_t received_cnt; + + mail::callback::message *msgCallback; + +public: + ReadMessageTask(mail::pop3 &serverArg, + mail::callback::message &callbackArg, + string uidArg, + size_t messageNumArg, + bool peekArg, + mail::readMode readTypeArg); + ~ReadMessageTask(); + + void loggedIn(); + void loginFailed(string errmsg); + + void serverResponse(const char *message); +}; + +mail::pop3::ReadMessageTask::ReadMessageTask(mail::pop3 &serverArg, + mail::callback::message + &callbackArg, + string uidArg, + size_t messageNumArg, + bool peekArg, + mail::readMode readTypeArg) + : LoggedInTask(&callbackArg, serverArg), + uid(uidArg), + messageNum(messageNumArg), + peek(peekArg), + readType(readTypeArg), + msgCallback(&callbackArg) +{ +} + +mail::pop3::ReadMessageTask::~ReadMessageTask() +{ +} + +void mail::pop3::ReadMessageTask::loggedIn() +{ + map<string, int>::iterator p=myserver->uidlMap.find(uid); + + if (p == myserver->uidlMap.end()) + { + loginFailed("Message was deleted by the POP3 server."); + return; + } + + int msgNum=p->second; + + seenHeaders=false; + currentHeader=""; + expectingStatus=true; + + if (!fixMessageNumber(myserver, uid, messageNum)) + { + expectingStatus=false; + serverResponse("."); + return; + } + + string buffer; + + { + ostringstream o; + + o << msgNum; + buffer=o.str(); + } + + string cmd; + + expected_cnt=0; + received_cnt=0; + + if (readType == mail::readContents || + readType == mail::readBoth) + { + cmd=string("RETR ") + buffer + "\r\n"; + + map<int, unsigned long>::iterator p + =myserver->listMap.find(msgNum); + + if (p != myserver->listMap.end()) + expected_cnt=p->second; + } + else + cmd=string("TOP ") + buffer + " 0\r\n"; + + myserver->socketWrite(cmd); +} + +void mail::pop3::ReadMessageTask::loginFailed(string errmsg) +{ + mail::callback *c=callbackPtr; + + callbackPtr=NULL; + + done(); + + if (c) + c->fail(errmsg); +} + +void mail::pop3::ReadMessageTask::serverResponse(const char *message) +{ + if (expectingStatus) + { + expectingStatus=false; + if (*message != '+') + loginFailed(message); + return; + } + + time(&defaultTimeout); + defaultTimeout += myserver->timeoutSetting; + // Some activity -- don't time out + + if (strcmp(message, ".") == 0) + { + mail::callback::message *c=msgCallback; + + mail::ptr<mail::pop3> ptr= myserver; + + bool peekFlag=peek; + + if (!fixMessageNumber(myserver, uid, messageNum)) + peekFlag=true; + + if (callbackPtr == NULL) + c=NULL; + + callbackPtr=NULL; + msgCallback=NULL; + + if (currentHeader.size() > 0) + { + if (c) + c->messageTextCallback(0, + currentHeader + "\n"); + } + + size_t n=messageNum; + + received_cnt=expected_cnt; + if (c) + c->reportProgress(expected_cnt, expected_cnt, 1, 1); + + done(); + + if (c) + c->success("OK"); + + if (!peekFlag && !ptr.isDestroyed()) + { + ptr->currentFolderIndex[n].unread=false; + if (ptr->folderCallback) + ptr->folderCallback + ->messageChanged(n); + + if (!ptr.isDestroyed() && ptr->folderCallback) + ptr->folderCallback->saveSnapshot("POP3"); + } + + return; + } + + if (*message == '.') + ++message; + + received_cnt += strlen(message)+2; + + if (callbackPtr && msgCallback) + msgCallback->reportProgress(received_cnt, expected_cnt, 0, 1); + + + if (readType == mail::readHeadersFolded) + // Reformat headers, one per line + { + if (!*message) // End of headers + { + string s=currentHeader; + + currentHeader=""; + + if (s.size() > 0 && callbackPtr && msgCallback) + msgCallback->messageTextCallback(0, s + "\n"); + return; + } + + if (unicode_isspace((unsigned char)*message)) + { + while (*message && + unicode_isspace((unsigned char)*message)) + ++message; + + if (currentHeader.size() > 0) + currentHeader += " "; + currentHeader += message; + return; + } + + string s=currentHeader; + currentHeader=message; + + if (callbackPtr && msgCallback && s.size() > 0) + msgCallback->messageTextCallback(0, s + "\n"); + return; + } + + if (readType == mail::readContents && !seenHeaders) + { + if (!*message) + seenHeaders=true; + return; // Eat the headers + } + + string line=message; + + line += "\n"; + + if (callbackPtr && msgCallback) + msgCallback->messageTextCallback(0, line); +} + + +///////////////////////////////////////////////////////////////////////////// + +class mail::pop3::CacheMessageTask : public mail::callback::message { + + mail::ptr<mail::pop3> myserver; + + mail::callback *callback; + + string uid; + + int *fdptr; + struct rfc2045 **rfcptr; + + FILE *tmpfilefp; + struct rfc2045 *rfc; + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + +public: + CacheMessageTask(mail::pop3 *myserverArg, + mail::callback *callbackArg, + string uidArg, + int *fdArg, + struct rfc2045 **rfcArg); + ~CacheMessageTask(); + + bool init(); + + void success(string); + void fail(string); + + void messageTextCallback(size_t n, string text); +}; + +mail::pop3::CacheMessageTask::CacheMessageTask(mail::pop3 *myserverArg, + mail::callback *callbackArg, + string uidArg, + int *fdArg, + struct rfc2045 **rfcArg) + : myserver(myserverArg), + callback(callbackArg), + uid(uidArg), + fdptr(fdArg), + rfcptr(rfcArg), + tmpfilefp(NULL), + rfc(NULL) +{ +} + +mail::pop3::CacheMessageTask::~CacheMessageTask() +{ + if (tmpfilefp) + fclose(tmpfilefp); + + if (rfc) + rfc2045_free(rfc); +} + +void mail::pop3 +::CacheMessageTask::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + if (callback) + callback->reportProgress(bytesCompleted, + bytesEstimatedTotal, + messagesCompleted, + messagesEstimatedTotal); +} + +void mail::pop3::CacheMessageTask::success(string message) +{ + if (myserver.isDestroyed()) + { + fail("POP3 server connection aborted."); + return; + } + + if (fflush(tmpfilefp) || ferror(tmpfilefp)) + { + fail(strerror(errno)); + return; + } + + if (myserver->genericTmpFd >= 0) + close(myserver->genericTmpFd); + + rfc2045_parse_partial(rfc); + + if (myserver->genericTmpRfcp) + { + rfc2045_free(myserver->genericTmpRfcp); + myserver->genericTmpRfcp=NULL; + } + + myserver->cachedUid=uid; + + if ((myserver->genericTmpFd=dup(fileno(tmpfilefp))) < 0) + { + fail(strerror(errno)); + return; + } + + fcntl(myserver->genericTmpFd, F_SETFD, FD_CLOEXEC); + + if (fdptr) + *fdptr=myserver->genericTmpFd; + + myserver->genericTmpRfcp=rfc; + + if (rfcptr) + *rfcptr=rfc; + + rfc=NULL; + + mail::callback *c=callback; + + callback=NULL; + + delete this; + c->success(message); +} + +void mail::pop3::CacheMessageTask::fail(string message) +{ + mail::callback *c=callback; + + callback=NULL; + + delete this; + c->fail(message); +} + +bool mail::pop3::CacheMessageTask::init() +{ + if ((tmpfilefp=tmpfile()) == NULL || + (rfc=rfc2045_alloc()) == NULL) + { + fail(strerror(errno)); + return false; + } + + return true; +} + +void mail::pop3::CacheMessageTask::messageTextCallback(size_t n, string text) +{ + if (fwrite(text.c_str(), text.size(), 1, tmpfilefp) != 1) + ; // Ignore gcc warning + rfc2045_parse(rfc, text.c_str(), text.size()); +} + + +///////////////////////////////////////////////////////////////////////////// + +#if 0 +mail::pop3::AutologoutCallback::AutologoutCallback() +{ +} + +mail::pop3::AutologoutCallback::~AutologoutCallback() +{ +} + +void mail::pop3::AutologoutCallback::success(string message) +{ + delete this; +} + +void mail::pop3::AutologoutCallback::fail(string message) +{ + delete this; +} +#endif + + +///////////////////////////////////////////////////////////////////////////// +// +// A NOOP msg - keep the server from timing out. + +class mail::pop3::NoopTask : public mail::pop3::LoggedInTask { +public: + NoopTask(mail::pop3 &myseverArg); + ~NoopTask(); + void serverResponse(const char *message); + void loggedIn(); + void loginFailed(string errmsg); +}; + +mail::pop3::NoopTask::NoopTask(mail::pop3 &serverArg) + : LoggedInTask(NULL, serverArg) +{ +} + +mail::pop3::NoopTask::~NoopTask() +{ +} + +void mail::pop3::NoopTask::loginFailed(string errmsg) +{ +} + +void mail::pop3::NoopTask::loggedIn() +{ + myserver->socketWrite("NOOP\r\n"); +} + +void mail::pop3::NoopTask::serverResponse(const char *message) +{ + done(); +} + + +///////////////////////////////////////////////////////////////////////////// + + +mail::pop3::pop3MessageInfo::pop3MessageInfo() +{ +} + +mail::pop3::pop3MessageInfo::~pop3MessageInfo() +{ +} + +///////////////////////////////////////////////////////////////////////////// + + +mail::pop3::pop3(string url, string passwd, + std::vector<std::string> &certificates, + mail::loginCallback *callback_func, + mail::callback &callback, + mail::callback::disconnect &disconnectCallback) + : mail::fd(disconnectCallback, certificates), + calledDisconnected(false), + orderlyShutdown(false), + lastTaskCompleted(0), + inbox(this), folderCallback(NULL), + genericTmpFd(-1), genericTmpRfcp(NULL) +{ + savedLoginInfo.callbackPtr=NULL; + savedLoginInfo.loginCallbackFunc=callback_func; + + if (!loginUrlDecode(url, savedLoginInfo)) + { + callback.fail("Invalid POP3 URL."); + return; + } + + timeoutSetting=getTimeoutSetting(savedLoginInfo, "timeout", 60, + 30, 600); + noopSetting=getTimeoutSetting(savedLoginInfo, "noop", 60, + 5, 1800); + + savedLoginInfo.use_ssl=savedLoginInfo.method == "pop3s"; + if (passwd.size() > 0) + savedLoginInfo.pwd=passwd; + + installTask(new CheckNewMailTask(*this, callback)); +} + +void mail::pop3::resumed() +{ + if (!tasks.empty()) + (*tasks.begin())->resetTimeout(); +} + +void mail::pop3::handler(vector<pollfd> &fds, int &ioTimeout) +{ + int fd_save=getfd(); + + if (!tasks.empty()) + { + int t=(*tasks.begin())->getTimeout() * 1000; + + if (t < ioTimeout) + ioTimeout=t; + } + + if (fd_save >= 0 && lastTaskCompleted) + { + time_t now; + + time(&now); + + if (now < lastTaskCompleted) + lastTaskCompleted=now; + + if (now >= lastTaskCompleted + noopSetting) + { + lastTaskCompleted=0; + + installTask(new NoopTask(*this)); + } + else + { + if (ioTimeout >= + (lastTaskCompleted + noopSetting - now) * 1000) + { + ioTimeout=(lastTaskCompleted + noopSetting + - now) * 1000; + } + } + } + + MONITOR(mail::account); + + if (process(fds, ioTimeout) < 0) + return; + + if (DESTROYED()) + ioTimeout=0; + + if (DESTROYED() || getfd() < 0) + { + size_t i; + + for (i=fds.size(); i; ) + { + --i; + + if (fds[i].fd == fd_save) + { + fds.erase(fds.begin()+i, fds.begin()+i+1); + break; + } + } + } +} + +int mail::pop3::socketRead(const string &readbuffer) +{ + mail::ptr<mail::pop3> myself=this; + + size_t n=0; + + string::const_iterator b=readbuffer.begin(), e=readbuffer.end(), c; + + c=b; + + while (b != e) + { + if (*b != '\n') + { + b++; + continue; + } + + string l=string(c, b++); + + n += b-c; + c=b; + + size_t i; + + while ((i=l.find('\r')) != std::string::npos) + l=l.substr(0, i) + l.substr(i+1); + + if (!tasks.empty()) + (*tasks.begin())->serverResponse(l.c_str()); + + break; + } + + if (myself.isDestroyed()) + return n; + + if (n + 16000 < readbuffer.size()) + n=readbuffer.size() - 16000; + // SANITY CHECK - DON'T LET HOSTILE SERVER DOS us + + return n; +} + +void mail::pop3::disconnect(const char *reason) +{ + if (!tasks.empty()) + { + if ( (*tasks.begin())->willReconnect()) + { + socketDisconnect(); + (*tasks.begin())->done(); + return; + } + } + + if (genericTmpFd >= 0) + { + close(genericTmpFd); + genericTmpFd= -1; + } + + if (genericTmpRfcp != NULL) + { + rfc2045_free(genericTmpRfcp); + genericTmpRfcp=NULL; + } + + string errmsg=reason ? reason:""; + + if (getfd() >= 0) + { + string errmsg2=socketDisconnect(); + + if (errmsg2.size() > 0) + errmsg=errmsg2; + + if (orderlyShutdown) + errmsg=""; + else if (errmsg.size() == 0) + errmsg="Connection closed by remote host."; + } + + while (!tasks.empty()) + (*tasks.begin()) + ->disconnected(errmsg.size() > 0 ? + errmsg.c_str() + : "POP3 server connection closed."); + + if (!calledDisconnected) + { + calledDisconnected=true; + disconnect_callback.disconnected(""); + } + +} + +void mail::pop3::installTask(Task *t) +{ + if (!t) + LIBMAIL_THROW("Out of memory."); + + bool wasEmpty; + + lastTaskCompleted=0; + + try { + wasEmpty=tasks.empty(); + + tasks.insert(tasks.end(), t); + } catch (...) { + delete t; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + if (wasEmpty) + (*tasks.begin())->installedTask(); +} + +mail::pop3::~pop3() +{ + disconnect("POP3 server connection aborted."); + currentFolderIndex.clear(); +} + +void mail::pop3::logout(mail::callback &callback) +{ + installTask(new LogoutTask(*this, &callback)); +} + +void mail::pop3::checkNewMail(mail::callback &callback) +{ + ForceCheckNewMailTask *reloginTask= + new ForceCheckNewMailTask(&callback, this); + + if (!reloginTask) + callback.fail("Out of memory"); + + try { + installTask(new LogoutTask(*this, reloginTask, true)); + } catch (...) { + delete reloginTask; + + callback.fail("Out of memory."); + } +} + +bool mail::pop3::hasCapability(string capability) +{ + if (capability == LIBMAIL_SINGLEFOLDER) + return true; + + return capabilities.count(capability) > 0; +} + +string mail::pop3::getCapability(string capability) +{ + upper(capability); + + if (capability == LIBMAIL_SERVERTYPE) + { + return "pop3"; + } + + map<string, string>::iterator p; + + if (capability == LIBMAIL_SERVERDESCR) + { + p=capabilities.find("IMPLEMENTATION"); + + return (p == capabilities.end() ? + (ispop3maildrop() ? "POP3 maildrop": + "POP3 account") + : + (ispop3maildrop() ? "POP3 maildrop - ":"POP3 - ") + + p->second); + } + + p=capabilities.find(capability); + + if (p == capabilities.end()) + return ""; + + return p->second; +} + +mail::folder *mail::pop3::folderFromString(string string) +{ + return inbox.clone(); +} + +void mail::pop3::readTopLevelFolders(mail::callback::folderList &callback1, + mail::callback &callback2) +{ + vector<const mail::folder *> folders; + + folders.push_back(&inbox); + + callback1.success(folders); + callback2.success("OK"); +} + +void mail::pop3::findFolder(string folder, + mail::callback::folderList &callback1, + mail::callback &callback2) +{ + return readTopLevelFolders(callback1, callback2); +} + +void mail::pop3::genericMessageRead(string uid, + size_t messageNumber, + bool peek, + mail::readMode readType, + mail::callback::message &callback) +{ + if (fixMessageNumber(this, uid, messageNumber) && + genericCachedUid(uid)) + { + // Optimum path + + vector<size_t> vec; + vec.push_back(messageNumber); + + readMessageContent(vec, peek, readType, callback); + return; + } + + installTask(new ReadMessageTask(*this, callback, + uid, + messageNumber, + peek, + readType)); +} + +void mail::pop3::genericMessageSize(string uid, + size_t messageNumber, + mail::callback::message &callback) +{ + if (uidlMap.count(uid) > 0) + { + int messageNum=uidlMap.find(uid)->second; + + if (listMap.count(messageNum) > 0 && + fixMessageNumber(this, uid, messageNumber)) + { + callback.messageSizeCallback(messageNumber, + listMap.find(messageNum) + ->second); + } + } + callback.success("OK"); +} + +void mail::pop3::readMessageAttributes(const vector<size_t> &messages, + MessageAttributes attributes, + mail::callback::message &callback) +{ + mail::generic::genericAttributes(this, this, messages, + attributes, + callback); +} + +void mail::pop3::genericGetMessageFd(string uid, + size_t messageNumber, + bool peek, + int &fdRet, + mail::callback &callback) +{ + if (genericTmpFd >= 0 && cachedUid == uid) + { + fdRet=genericTmpFd; + callback.success("OK"); + return; + } + + if (uidlMap.count(uid) == 0) + { + callback.fail("Message removed from the POP3 server."); + return; + } + + CacheMessageTask *t= + new CacheMessageTask(this, &callback, uid, &fdRet, NULL); + + if (!t || !t->init()) + { + if (t) + delete t; + + callback.fail("Out of memory."); + } + + try { + genericMessageRead(uid, messageNumber, peek, + mail::readBoth, *t); + } catch (...) { + delete t; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + + +void mail::pop3::genericGetMessageStruct(string uid, + size_t messageNumber, + struct rfc2045 *&structRet, + mail::callback &callback) +{ + if (genericTmpRfcp && cachedUid == uid) + { + structRet=genericTmpRfcp; + callback.success("OK"); + return; + } + + if (uidlMap.count(uid) == 0) + { + callback.fail("Message removed from the POP3 server."); + return; + } + + CacheMessageTask *t= + new CacheMessageTask(this, &callback, uid, NULL, &structRet); + + if (!t || !t->init()) + { + if (t) + delete t; + + callback.fail("Out of memory."); + } + + try { + genericMessageRead(uid, messageNumber, true, + mail::readBoth, *t); + } catch (...) { + delete t; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + +} + +bool mail::pop3::genericCachedUid(string uid) +{ + return genericTmpRfcp && cachedUid == uid; +} + +void mail::pop3::readMessageContent(const vector<size_t> &messages, + bool peek, + enum mail::readMode readType, + mail::callback::message &callback) +{ + genericReadMessageContent(this, this, messages, peek, readType, + callback); +} + +void mail::pop3::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::pop3::readMessageContentDecoded(size_t messageNum, + bool peek, + const mail::mimestruct &msginfo, + mail::callback::message &callback) +{ + genericReadMessageContentDecoded(this, this, messageNum, + peek, msginfo, callback); +} + +size_t mail::pop3::getFolderIndexSize() +{ + return currentFolderIndex.size(); +} + +mail::messageInfo mail::pop3::getFolderIndexInfo(size_t n) +{ + if (n < currentFolderIndex.size()) + return currentFolderIndex[n]; + + return mail::messageInfo(); +} + + +void mail::pop3::saveFolderIndexInfo(size_t messageNum, + const mail::messageInfo &info, + mail::callback &callback) +{ + MONITOR(mail::pop3); + + if (messageNum < currentFolderIndex.size()) + { + +#define DOFLAG(dummy1, field, dummy2) \ + (currentFolderIndex[messageNum].field= info.field) + + LIBMAIL_MSGFLAGS; + +#undef DOFLAG + + } + + callback.success("OK"); + + if (! DESTROYED() && messageNum < currentFolderIndex.size() + && folderCallback) + { + folderCallback->messageChanged(messageNum); + + if (!DESTROYED() && folderCallback) + folderCallback->saveSnapshot("POP3"); + } + +} + +void mail::pop3::genericMarkRead(size_t messageNumber) +{ + if (messageNumber < currentFolderIndex.size() && + currentFolderIndex[messageNumber].unread) + { + MONITOR(mail::pop3); + + currentFolderIndex[messageNumber].unread=false; + if (folderCallback) + folderCallback->messageChanged(messageNumber); + if (! DESTROYED() && folderCallback) + folderCallback->saveSnapshot("POP3"); + + } +} + +void mail::pop3::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=currentFolderIndex.size(); + + while (b != e) + { + size_t i= *b++; + + if (i < n) + { +#define DOFLAG(dummy, field, dummy2) \ + if (flags.field) \ + { \ + currentFolderIndex[i].field=\ + doFlip ? !currentFolderIndex[i].field\ + : enableDisable; \ + } + + LIBMAIL_MSGFLAGS; +#undef DOFLAG + } + } + + b=messages.begin(); + e=messages.end(); + + MONITOR(mail::pop3); + + while (!DESTROYED() && b != e) + { + size_t i= *b++; + + if (i < n && folderCallback) + folderCallback->messageChanged(i); + } + + if (!DESTROYED()) + folderCallback->saveSnapshot("POP3"); + + callback.success("OK"); +} + +void mail::pop3::getFolderKeywordInfo(size_t n, set<string> &kwSet) +{ + kwSet.clear(); + + if (n < currentFolderIndex.size()) + currentFolderIndex[n].keywords.getFlags(kwSet); +} + +void mail::pop3::updateKeywords(const std::vector<size_t> &messages, + const std::set<std::string> &keywords, + bool setOrChange, + // false: set, true: see changeTo + bool changeTo, + callback &cb) +{ + MONITOR(mail::pop3); + + genericUpdateKeywords(messages, keywords, setOrChange, changeTo, + folderCallback, this, cb); + + if (!DESTROYED()) + folderCallback->saveSnapshot("POP3"); +} + +bool mail::pop3::genericProcessKeyword(size_t messageNumber, + updateKeywordHelper &helper) +{ + if (messageNumber < currentFolderIndex.size()) + helper.doUpdateKeyword(currentFolderIndex[messageNumber] + .keywords, keywordHashtable); + return true; +} + +void mail::pop3::updateFolderIndexInfo(mail::callback &callback) +{ + installTask(new UpdateTask(*this, callback)); +} + +void mail::pop3::removeMessages(const std::vector<size_t> &messages, + callback &cb) +{ + genericRemoveMessages(this, messages, cb); +} + +void mail::pop3::copyMessagesTo(const vector<size_t> &messages, + mail::folder *copyTo, + mail::callback &callback) +{ + mail::copyMessages::copy(this, messages, copyTo, callback); +} + +void mail::pop3::searchMessages(const mail::searchParams &searchInfo, + mail::searchCallback &callback) +{ + switch (searchInfo.criteria) { + case searchParams::larger: + case searchParams::smaller: + case searchParams::body: + case searchParams::text: + callback.fail("Not implemented."); + return; + default: + break; + } + mail::searchMessages::search(callback, searchInfo, this); +} + +void mail::pop3Folder::getParentFolder(callback::folderList &callback1, + callback &callback2) const +{ + vector<const mail::folder *> dummy; + + dummy.push_back(this); + + callback1.success(dummy); + callback2.success("OK"); +} + +void mail::pop3::readFolderInfo( mail::callback::folderInfo &callback1, + mail::callback &callback2) +{ + callback1.messageCount=currentFolderIndex.size(); + callback1.unreadCount=0; + + size_t i=0; + + for (i=0; i<callback1.messageCount; i++) + if (currentFolderIndex[i].unread) + ++callback1.unreadCount; + + callback1.success(); + callback2.success("OK"); +} + +void mail::pop3::open(mail::callback &openCallback, + mail::callback::folder &folderCallbackArg, + mail::snapshot *restoreSnapshot) + +{ + + if (restoreSnapshot) + { + string snapshotId; + size_t nmessages; + + folderCallback=NULL; + + restoreSnapshot->getSnapshotInfo(snapshotId, nmessages); + + if (snapshotId.size() > 0) + { + currentFolderIndex.clear(); + currentFolderIndex.resize(nmessages); + restoreAborted=false; + restoreSnapshot->restoreSnapshot(*this); + + if (!restoreAborted) + { + vector<pop3MessageInfo>::iterator b, e; + + b=currentFolderIndex.begin(); + e=currentFolderIndex.end(); + + // Sanity check: + + while (b != e) + { + if ( b->uid.size() == 0) + { + restoreAborted=true; + break; + } + b++; + } + } + + if (!restoreAborted) + { + folderCallback= &folderCallbackArg; + reconcileFolderIndex(); + openCallback.success("OK"); + return; + } + + // Rebuild folder index. + + currentFolderIndex.clear(); + reconcileFolderIndex(); + } + } + + folderCallback= &folderCallbackArg; + openCallback.success("OK"); +} + +void mail::pop3::restoreIndex(size_t msgNum, + const mail::messageInfo &info) +{ + if (msgNum < currentFolderIndex.size()) + (static_cast<mail::messageInfo &> + (currentFolderIndex[msgNum]))=info; +} + +void mail::pop3::restoreKeywords(size_t msgNum, + const std::set<std::string> &kwSet) +{ + if (msgNum < currentFolderIndex.size()) + currentFolderIndex[msgNum].keywords + .setFlags(keywordHashtable, kwSet); +} + +void mail::pop3::abortRestore() +{ + restoreAborted=true; +} + + + +// +// Reconcile the virtual folder index to reflect what's actually on the +// server. +// + +bool mail::pop3::reconcileFolderIndex() +{ + set<size_t> messagesSeen; + + map<string, int>::iterator ub, ue; + + size_t i; + + bool needSnapshot=false; + + // Make a list of all msgs that exist (and don't exist any more) + + mail::expungeList removedList; + + for (i=currentFolderIndex.size(); i; ) + { + string uid=currentFolderIndex[--i].uid; + + if (uidlMap.count( uid ) == 0) + { + removedList << i; + needSnapshot=true; + } + else + messagesSeen.insert(uidlMap.find(uid)->second); + } + + // Send expunge notices + + MONITOR(mail::pop3); + + mail::expungeList::iterator expungeIterator; + + for (expungeIterator=removedList.end(); + expungeIterator != removedList.begin(); ) + { + --expungeIterator; + currentFolderIndex.erase(currentFolderIndex.begin() + + expungeIterator->first, + currentFolderIndex.begin() + + expungeIterator->second+1); + } + + if (folderCallback) + removedList >> folderCallback; + + // Now check for new messages. + + if (!DESTROYED()) + { + vector<string> newUids; + + ub=uidlMap.begin(); + ue=uidlMap.end(); + + // sortNewMail() will end up sorting newUids chronologically. + + while (ub != ue) + { + if (messagesSeen.count(ub->second) == 0) + newUids.push_back(ub->first); + ub++; + } + sort(newUids.begin(), newUids.end(), + CheckNewMailTask::sortNewMail(uidlMap)); + + vector<string>::iterator b, e; + + b=newUids.begin(), e=newUids.end(); + + while (b != e) + { + pop3MessageInfo newInfo; + + // We'll have to assume that all msgs are unread. + + newInfo.unread=true; + newInfo.uid= *b++; + currentFolderIndex.push_back(newInfo); + } + + if (newUids.size() > 0 && folderCallback) + // Note folderCallback is NULL on account open! + { + needSnapshot=true; + folderCallback->newMessages(); + } + } + return needSnapshot; +} + +////////////////////// +// +// Stubs, overridden by mail::pop3maildrop::pop3acct + +bool mail::pop3::ispop3maildrop() +{ + return false; +} + +void mail::pop3::pop3maildropreset() +{ +} + +mail::addMessage *mail::pop3::newDownloadMsg() +{ + return NULL; +} + +string mail::pop3::commitDownloadedMsgs() +{ + return ""; +} diff --git a/libmail/pop3.H b/libmail/pop3.H new file mode 100644 index 0000000..edd4a0f --- /dev/null +++ b/libmail/pop3.H @@ -0,0 +1,400 @@ +/* +** Copyright 2002-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_pop3_H +#define libmail_pop3_H + +#include "libmail_config.h" +#if HAVE_FCNTL_H +#include <fcntl.h> +#endif +#include "mail.H" +#include <sys/types.h> + +#include "maildir/maildirkeywords.h" +#include "logininfo.H" +#include "fd.H" +#include "generic.H" +#include "snapshot.H" +#include <stdio.h> +#include <time.h> +#include <string> +#include <map> +#include <list> +#include <vector> + +/////////////////////////////////////////////////////////////////////////// +// +// An POP3 implementation +// +// The POP3 INBOX folder + +LIBMAIL_START + +class pop3; +class addMessage; + +class pop3Folder : public folder { + + void sameServerAsHelperFunc() const; + + pop3 *server; + +public: + pop3Folder(pop3 *serverArg); + ~pop3Folder(); + + std::string getName() const; + std::string getPath() const; + + bool hasMessages() const; + bool hasSubFolders() const; + bool isParentOf(std::string path) const; + + void hasMessages(bool); + void hasSubFolders(bool); + + void getParentFolder(callback::folderList &callback1, + callback &callback2) const; + + void readFolderInfo( class callback::folderInfo + &callback1, + callback &callback2) const; + + void readSubFolders( callback::folderList &callback1, + callback &callback2) const; + + mail::addMessage *addMessage(callback &callback) const; + + void createSubFolder(std::string name, bool isDirectory, + class callback::folderList &callback1, + callback &callback2) const; + + void create(bool isDirectory, + callback &callback) const; + + void destroy(callback &callback, bool destroyDir) const; + + void renameFolder(const mail::folder *newParent, std::string newName, + mail::callback::folderList &callback1, + callback &callback) const; + + folder *clone() const; + std::string toString() const; + + void open(class callback &openCallback, + snapshot *restoreSnapshot, + class callback::folder &folderCallback) const; +}; + + +////////////////////////////////////////////////////////////////////////////// + +class pop3 : public fd, public generic, public snapshot::restore { + +private: + bool calledDisconnected; + // True when the disconnect callback is invoked + + bool orderlyShutdown; + + time_t timeoutSetting; + time_t noopSetting; + + // Superclass of POP3 tasks. Methods create subclasses of Task + // objects, and push them to the tasks queue. Replies from the + // POP3 server are routed to the foremost Task on the queue. + + class Task { + + protected: + callback *callbackPtr; // App callback. + pop3 * volatile myserver; + // Marked volatile due to destructor monkey business + + time_t defaultTimeout; + + public: + virtual void done(); + // Task completed, start the next task on the queue, + // and delete this Task object. + + Task(callback *callbackArg, + pop3 &myserverArg); + virtual ~Task(); + + virtual int getTimeout(); + // How long before this task times out. + + virtual void serverResponse(const char *message)=0; + // Process a line of text from the server + + virtual void disconnected(const char *reason); + // Server has disconnected. + // The default implementation takes this task off the queue, + // calls the next Task's disconnect method, then deletes + // itself. + + virtual void installedTask(); + // This task is now at the front of the queue + + void resetTimeout(); + // Reset timeout interval + + virtual bool isLogoutTask(); + // This is a logout task. If it's not the logout task, + // its completion time is recorded. If nothing happens, + // a new NOOP task is started to keep the server from timing + // out. + + virtual bool willReconnect(); + // This logout task is a part of expunge processing, so + // do not do disconnect processing. + }; + +#if 0 + class AutologoutCallback : public callback { + public: + AutologoutCallback(); + ~AutologoutCallback(); + + void success(std::string message); + void fail(std::string message); + }; +#endif + + void resumed(); + void handler(std::vector<pollfd> &fds, int &timeout); + + std::map<std::string, std::string> capabilities; + + loginInfo pop3LoginInfo; + + loginInfo savedLoginInfo; + + int socketRead(const std::string &readbuffer); + + void disconnect(const char *reason); + + std::list<Task *> tasks; + + void installTask(Task *p); + + time_t lastTaskCompleted; + + class LoginTask; + class LogoutTask; + class LoggedInTask; + + class CheckNewMailTask; + class ReadMessageTask; + class ForceCheckNewMailTask; + class CacheMessageTask; + class UpdateTask; + class NoopTask; + + pop3Folder inbox; + + // A map from message UID to the current message number in the POP3 + // server. + std::map<std::string, int> uidlMap; + + std::map<int, unsigned long> listMap; + // Size of each msg, in bytes. Should be an array, though, but that's + // ok. + + + callback::folder *folderCallback; + // The folder callback. + // POP3 implements snapshots for the purpose of preserving message + // status flags. There is no concept of message status flags in POP3, + // so by default, unless snapshots are used, each message is marked + // as a new message, when the POP3 folder is opened. If snapshots are + // used, the message status flags will be restored from the snapshot. + // The snapshot ID is always fixed, "POP3", since POP3 keys off msg + // UIDs entirely, so a snapshot restore simply restores whatever + // snapshot UIDs are available, then does a checknewmail. + // + // A snapshot is also saved each time message status flags are updated. + + // The virtual folder. + + class pop3MessageInfo : public messageInfo { + public: + pop3MessageInfo(); + ~pop3MessageInfo(); + + mail::keywords::Message keywords; + }; + + mail::keywords::Hashtable keywordHashtable; + + std::vector<pop3MessageInfo> currentFolderIndex; + // Virtual folder index + +public: + friend class Task; + friend class LoginTask; + friend class LogoutTask; + + friend class CheckNewMailTask; + friend class ReadMessageTask; + friend class ForceCheckNewMailTask; + friend class CacheMessageTask; + friend class UpdateTask; + friend class NoopTask; + + pop3(std::string url, std::string passwd, + std::vector<std::string> &certificates, + mail::loginCallback *loginCallbackFunc, + callback &callback, + callback::disconnect &disconnectCallback); + + pop3(const pop3 &); // UNDEFINED + pop3 &operator=(const pop3 &); // UNDEFINED + + ~pop3(); + + void logout(callback &callback); + + void checkNewMail(callback &callback); + bool hasCapability(std::string capability); + std::string getCapability(std::string capability); + + folder *folderFromString(std::string); + + void readTopLevelFolders(callback::folderList &callback1, + callback &callback2); + void findFolder(std::string folder, + class callback::folderList &callback1, + class callback &callback2); + std::string translatePath(std::string path); + + void readMessageAttributes(const std::vector<size_t> &messages, + MessageAttributes attributes, + callback::message &callback); + + void readMessageContent(const std::vector<size_t> &messages, + bool peek, + enum mail::readMode readType, + callback::message &callback); + + void readMessageContent(size_t messageNum, + bool peek, + const mimestruct &msginfo, + enum mail::readMode readType, + callback::message &callback); + + void readMessageContentDecoded(size_t messageNum, + bool peek, + const mimestruct &msginfo, + callback::message &callback); + + size_t getFolderIndexSize(); + messageInfo getFolderIndexInfo(size_t); + + void saveFolderIndexInfo(size_t, + const messageInfo &, + callback &); + void getFolderKeywordInfo(size_t, std::set<std::string> &); + void updateKeywords(const std::vector<size_t> &messages, + const std::set<std::string> &keywords, + bool setOrChange, + // false: set, true: see changeTo + bool changeTo, + callback &cb); +private: + bool genericProcessKeyword(size_t messageNumber, + updateKeywordHelper &helper); +public: + void updateFolderIndexFlags(const std::vector<size_t> &messages, + bool doFlip, + bool enableDisable, + const messageInfo &flags, + callback &callback); + + void updateFolderIndexInfo(callback &); + void removeMessages(const std::vector<size_t> &messages, + callback &cb); + + void copyMessagesTo(const std::vector<size_t> &messages, + folder *copyTo, + callback &callback); + + void searchMessages(const searchParams &searchInfo, + searchCallback &callback); + + // Used by pop3Folder: + + void readFolderInfo( callback::folderInfo &callback1, + callback &callback2); + + void open(callback &openCallback, + callback::folder &folderCallback, + mail::snapshot *restoreSnapshot); + + + bool reconcileFolderIndex(); + +private: + + void genericMarkRead(size_t messageNumber); + + void genericMessageRead(std::string uid, + size_t messageNumber, + bool peek, + mail::readMode readType, + callback::message &callback); + + void genericMessageSize(std::string uid, + size_t messageNumber, + callback::message &callback); + + void genericGetMessageFd(std::string uid, + size_t messageNumber, + bool peek, + int &fdRet, + callback &callback); + + void genericGetMessageStruct(std::string uid, + size_t messageNumber, + struct rfc2045 *&structRet, + callback &callback); + + bool genericCachedUid(std::string uid); + + // One message is cached to a temp file, and parsed. + + std::string cachedUid; + int genericTmpFd; + struct rfc2045 *genericTmpRfcp; + + // + // Inherited from mail::snapshot::restore: + // + + void restoreIndex(size_t msgNum, + const mail::messageInfo &info); + void restoreKeywords(size_t msgNum, + const std::set<std::string> &); + void abortRestore(); + + bool restoreAborted; + + +protected: + // Subclassed by mail::pop3maildrop::pop3acct(): + + virtual bool ispop3maildrop(); + virtual void pop3maildropreset(); + + virtual mail::addMessage *newDownloadMsg(); + virtual std::string commitDownloadedMsgs(); + +}; + +LIBMAIL_END + +#endif diff --git a/libmail/pop3folder.C b/libmail/pop3folder.C new file mode 100644 index 0000000..31deb3f --- /dev/null +++ b/libmail/pop3folder.C @@ -0,0 +1,140 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" + +#include "mail.H" +#include "pop3.H" + +using namespace std; + +mail::pop3Folder::pop3Folder(mail::pop3 *serverArg) + : mail::folder(serverArg), server(serverArg) +{ +} + +mail::pop3Folder::~pop3Folder() +{ +} + +void mail::pop3Folder::sameServerAsHelperFunc() const +{ +} + +string mail::pop3Folder::getName() const +{ + return "INBOX"; +} + +string mail::pop3Folder::getPath() const +{ + return getName(); +} + +bool mail::pop3Folder::hasMessages() const +{ + return true; +} + +bool mail::pop3Folder::hasSubFolders() const +{ + return false; +} + +bool mail::pop3Folder::isParentOf(string path) const +{ + return false; +} + +void mail::pop3Folder::hasMessages(bool flag) +{ +} + +void mail::pop3Folder::hasSubFolders(bool flag) +{ +} + +void mail::pop3Folder::readFolderInfo( mail::callback::folderInfo &callback1, + mail::callback &callback2) const +{ + if (isDestroyed(callback2)) + return; + + server->readFolderInfo(callback1, callback2); +} + +void mail::pop3Folder::readSubFolders( mail::callback::folderList &callback1, + mail::callback &callback2) const +{ + if (isDestroyed(callback2)) + return; + + vector<const mail::folder *> dummy; + + callback1.success(dummy); + callback2.success("No folders exist on a POP3 server."); +} + +mail::addMessage *mail::pop3Folder::addMessage(mail::callback &callback) const +{ + callback.fail("Messages cannot be copied to this POP3 server account."); + return NULL; +} + +void mail::pop3Folder::createSubFolder(string name, bool isDirectory, + mail::callback::folderList &callback1, + mail::callback &callback2) const +{ + callback2.fail("Folders cannot be created in this POP3 server account."); +} + +void mail::pop3Folder::create(bool isDirectory, mail::callback &callback) const +{ + callback.fail("Folders cannot be created in this POP3 server account."); +} + +void mail::pop3Folder::destroy(mail::callback &callback, bool destroyDir) const +{ + callback.fail("You cannot delete this POP3 folder. It's a fake," + " and how did you get this error message anyway?"); +} + +void mail::pop3Folder::renameFolder(const mail::folder *newParent, + std::string newName, + mail::callback::folderList &callback1, + callback &callback) const +{ + callback.fail("POP3 folders cannot be renamed."); +} + +mail::folder *mail::pop3Folder::clone() const +{ + mail::pop3Folder *p=new mail::pop3Folder(server); + + if (!p) + return NULL; + + return p; +} + +string mail::pop3Folder::toString() const +{ + return getPath(); +} + +void mail::pop3Folder::open(mail::callback &openCallback, + mail::snapshot *restoreSnapshot, + mail::callback::folder &folderCallback) const +{ + if (isDestroyed(openCallback)) + return; + server->open(openCallback, folderCallback, restoreSnapshot); +} + +string mail::pop3::translatePath(string path) +{ + return "INBOX"; // NOOP +} + diff --git a/libmail/pop3maildrop.C b/libmail/pop3maildrop.C new file mode 100644 index 0000000..6340dd7 --- /dev/null +++ b/libmail/pop3maildrop.C @@ -0,0 +1,440 @@ +/* +** Copyright 2002-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "pop3maildrop.H" +#include "pop3.H" +#include "maildiradd.H" +#include "driver.H" +#include "misc.H" + +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> + +using namespace std; + +///////////////////////////////////////////////////////////////////////////// + +LIBMAIL_START + +static bool open_pop3maildrop(mail::account *&accountRet, + mail::account::openInfo &oi, + mail::callback &callback, + mail::callback::disconnect &disconnectCallback) +{ + mail::loginInfo pop3LoginInfo; + + if (!mail::loginUrlDecode(oi.url, pop3LoginInfo)) + return false; + + string pop3method; + + if (pop3LoginInfo.method == "pop3maildrop") + pop3method="pop3"; + else if (pop3LoginInfo.method == "pop3maildrops") + pop3method="pop3s"; + else + return false; + + accountRet=new mail::pop3maildrop(disconnectCallback, + oi.loginCallbackObj, + callback, + oi.extraString, + pop3method + + oi.url.substr(oi.url.find(':')), + oi.pwd, + oi.certificates); + + return true; +} + +static bool pop3maildrop_remote(string url, bool &flag) +{ + mail::loginInfo pop3LoginInfo; + + if (!mail::loginUrlDecode(url, pop3LoginInfo)) + return false; + + if (pop3LoginInfo.method != "pop3maildrop" && + pop3LoginInfo.method != "pop3maildrops") + return false; + + flag=false; + return true; +} + +driver pop3maildrop_driver= { &open_pop3maildrop, &pop3maildrop_remote }; + +LIBMAIL_END + + +class mail::pop3maildrop::checkNewMailHelper + : public mail::callback { + + size_t messagesProcessed; + +public: + mail::pop3maildrop::pop3acct *pop3acct; +private: + mail::pop3maildrop *maildiracct; + + mail::callback *origCallback; + + void success(std::string message); + void fail(std::string message); + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + +public: + + checkNewMailHelper(mail::pop3maildrop::pop3acct *pop3acctArg, + mail::pop3maildrop *maildiracctArg, + mail::callback *origCallbackArg); + ~checkNewMailHelper(); +}; + +///////////////////////////////////////////////////////////////////////////// +// +// Subclass pop3 to forego UIDL, and just push all msgs to the pop3maildrop +// maildir. + +class mail::pop3maildrop::pop3acct : public mail::pop3, + private mail::callback { + + mail::pop3maildrop *myMaildrop; + +public: + friend class mail::pop3maildrop::checkNewMailHelper; + + pop3acct(mail::pop3maildrop *myMaildropArg, + std::string url, std::string passwd, + std::vector<std::string> &certificates, + mail::loginCallback *loginCallbackFunc, + callback &callback, + callback::disconnect &disconnectCallback); + ~pop3acct(); + + vector<mail::addMessage *> downloadedMsgs; + size_t messageCount; + + bool ispop3maildrop(); + void pop3maildropreset(); + mail::addMessage *newDownloadMsg(); + string commitDownloadedMsgs(); + +private: + bool errflag; + string errmsg; + + // Inherited from mail::callback + + void success(std::string message); + void fail(std::string message); + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + + +}; + + +mail::pop3maildrop::pop3acct::pop3acct(mail::pop3maildrop *myMaildropArg, + std::string url, std::string passwd, + std::vector<std::string> &certificates, + mail::loginCallback *loginCallbackFunc, + callback &callback, + callback::disconnect + &disconnectCallback) + : pop3(url, passwd, certificates, + loginCallbackFunc, callback, disconnectCallback), + myMaildrop(myMaildropArg) +{ +} + +mail::pop3maildrop::pop3acct::~pop3acct() +{ + pop3maildropreset(); +} + +bool mail::pop3maildrop::pop3acct::ispop3maildrop() +{ + return true; +} + +void mail::pop3maildrop::pop3acct::pop3maildropreset() +{ + vector<mail::addMessage *>::iterator b=downloadedMsgs.begin(), + e=downloadedMsgs.end(); + + while (b != e) + { + if (*b) + (*b)->fail("Cancelled."); + *b=NULL; + ++b; + } + + downloadedMsgs.clear(); + errflag=false; + return; +} + +mail::addMessage *mail::pop3maildrop::pop3acct::newDownloadMsg() +{ + downloadedMsgs.push_back(NULL); + + return (downloadedMsgs.end()[-1]=new + mail::maildir::addmessage(myMaildrop, + myMaildrop->path, *this)); +} + +string mail::pop3maildrop::pop3acct::commitDownloadedMsgs() +{ + messageCount=downloadedMsgs.size(); + + vector<mail::addMessage *>::iterator b=downloadedMsgs.begin(), + e=downloadedMsgs.end(); + + while (b != e) + { + if (*b) + (*b)->go(); + *b=NULL; + ++b; + } + downloadedMsgs.clear(); + if (!errflag) + errmsg=""; + myMaildrop->maildir::checkNewMail(NULL); + return errmsg; +} + +void mail::pop3maildrop::pop3acct::success(std::string message) +{ +} + +void mail::pop3maildrop::pop3acct::fail(std::string message) +{ + errflag=true; + errmsg=message; +} + +void mail::pop3maildrop +::pop3acct::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ +} + +///////////////////////////////////////////////////////////////////////////// + +mail::pop3maildrop::pop3maildrop(mail::callback::disconnect + &disconnect_callback, + mail::loginCallback *loginCallbackFunc, + mail::callback &callback, + std::string pathArg, + std::string pop3url, + std::string pop3pass, + std::vector<std::string> &certificates) + : maildir((mail::callback::disconnect &)*this), + myPop3Acct(NULL) +{ + struct stat stat_buf; + + // Automatically create the maildir, if it doesn't exist. + + if (stat(pathArg.c_str(), &stat_buf) < 0 && errno == ENOENT) + { + if (!mail::maildir::maildirmake(pathArg, false)) + { + callback.fail(strerror(errno)); + return; + } + } + + if (!init(callback, pathArg)) + return; + maildir::ispop3maildrop=true; + + mail::loginInfo pop3LoginInfo; + + if (!mail::loginUrlDecode(pop3url, pop3LoginInfo) || + (pop3LoginInfo.method != "pop3" && + pop3LoginInfo.method != "pop3s")) + { + callback.fail("Invalid POP3 maildrop URL"); + return; + } + + mail::pop3maildrop::checkNewMailHelper *h= + new mail::pop3maildrop::checkNewMailHelper(NULL, + this, + &callback); + if (!h) + { + callback.fail(strerror(errno)); + return; + } + + try + { + myPop3Acct=new pop3acct(this, + pop3url, + pop3pass, + certificates, + loginCallbackFunc, + *h, + disconnect_callback); + if (!myPop3Acct) + { + delete h; + callback.fail(strerror(errno)); + } + h->pop3acct=myPop3Acct; + } + catch (...) + { + delete h; + throw; + } +} + + +bool mail::pop3maildrop::hasCapability(string capability) +{ + if (capability == LIBMAIL_SINGLEFOLDER) + return true; + + return maildir::hasCapability(capability); +} + +string mail::pop3maildrop::getCapability(string capability) +{ + if (capability == LIBMAIL_SERVERTYPE) + { + return "pop3maildrop"; + } + + if (myPop3Acct) + { + if (capability == LIBMAIL_SERVERDESCR) + { + return myPop3Acct->getCapability(capability); + } + } + + return maildir::getCapability(capability); +} + + +// Callbacks for maildir's disconnect/servererror logic: + +void mail::pop3maildrop::disconnected(const char *errmsg) +{ +} + +void mail::pop3maildrop::servererror(const char *errmsg) +{ +} + +mail::pop3maildrop::~pop3maildrop() +{ + if (myPop3Acct) + delete myPop3Acct; +} + +void mail::pop3maildrop::logout(callback &callback) +{ + if (myPop3Acct) + myPop3Acct->logout(callback); + else + maildir::logout(callback); +} + +mail::pop3maildrop::checkNewMailHelper +::checkNewMailHelper(mail::pop3maildrop::pop3acct *pop3acctArg, + mail::pop3maildrop *maildiracctArg, + mail::callback *origCallbackArg) + : messagesProcessed(0), + pop3acct(pop3acctArg), + maildiracct(maildiracctArg), + origCallback(origCallbackArg) +{ +} + +mail::pop3maildrop::checkNewMailHelper::~checkNewMailHelper() +{ +} + +void mail::pop3maildrop::checkNewMailHelper::success(std::string message) +{ + if (pop3acct->messageCount > 0) + { + messagesProcessed += pop3acct->messageCount; + pop3acct->checkNewMail(*this); + return; + } + mail::callback *c=origCallback; + origCallback=NULL; + delete this; + c->success(message); + +} + +void mail::pop3maildrop::checkNewMailHelper::fail(std::string message) +{ + mail::callback *c=origCallback; + origCallback=NULL; + delete this; + c->fail(message); +} + +void mail::pop3maildrop::checkNewMailHelper +::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + origCallback->reportProgress(bytesCompleted, + bytesEstimatedTotal, + messagesCompleted+messagesProcessed, + messagesEstimatedTotal+messagesProcessed); +} + + +void mail::pop3maildrop::checkNewMail(callback &callback) +{ + if (folderCallback && strcasecmp(folderPath.c_str(), "INBOX") == 0 && + myPop3Acct) + { + mail::pop3maildrop::checkNewMailHelper *h= + new mail::pop3maildrop::checkNewMailHelper(myPop3Acct, + this, + &callback); + + if (!h) + { + callback.fail(strerror(errno)); + return; + } + + myPop3Acct->checkNewMail(*h); + return; + } + + mail::maildir::checkNewMail(callback); +} diff --git a/libmail/pop3maildrop.H b/libmail/pop3maildrop.H new file mode 100644 index 0000000..bbb56cb --- /dev/null +++ b/libmail/pop3maildrop.H @@ -0,0 +1,61 @@ +/* +** Copyright 2003-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_pop3maildrop_H +#define libmail_pop3maildrop_H + +#include "libmail_config.h" +#include "mail.H" +#include "maildir.H" + +//////////////////////////////////////////////////////////////////////// +// +// A POP3 maildrop is subclassed from mail::maildir, so that it looks like +// a maildir. It maintains a private mail::pop3 object that is used to +// feed grab mail from a pop3 maildrop, and copy it to the local maildir. +// + +LIBMAIL_START + +class pop3maildrop : private callback::disconnect, + // Swallow disconnect events from maildir + + public mail::maildir { + + class pop3acct; // Custom POP3 subclass + + pop3acct *myPop3Acct; + + class checkNewMailHelper; +public: + + friend class CheckNewMailHelper; + + pop3maildrop(mail::callback::disconnect &disconnect_callback, + mail::loginCallback *loginCallbackFunc, + mail::callback &callback, + std::string pathArg, + std::string pop3url, + std::string pop3pass, + std::vector<std::string> &certificates); + ~pop3maildrop(); + + // Hook into POP3: + void logout(callback &callback); + void checkNewMail(callback &callback); + + bool hasCapability(std::string capability); + std::string getCapability(std::string capability); +private: + // Inherited from callback::disconnect: + + void disconnected(const char *errmsg); + void servererror(const char *errmsg); + +}; + +LIBMAIL_END + +#endif diff --git a/libmail/qp.C b/libmail/qp.C new file mode 100644 index 0000000..0d30a45 --- /dev/null +++ b/libmail/qp.C @@ -0,0 +1,144 @@ +/* +** Copyright 2002-2009, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" + +#include "qp.H" + +#include <string.h> + +using namespace std; + +static inline int xdigit(char c) +{ + static const char xdigits[]="0123456789ABCDEFabcdef"; + + const char *p=strchr(xdigits, c); + + if (p == NULL) + return -1; + + int n= p - xdigits; + + if (n >= 16) + n -= 6; + return n; +} + +mail::decodeqp::decodeqp() + : decodeBuffer("") +{ +} + +mail::decodeqp::~decodeqp() +{ +} + +void mail::decodeqp::decode(string text) +{ + string decodedString=""; + + text=decodeBuffer + text; + + // Just decode as much as possible, and leave the rest for next time. + + string::iterator b=text.begin(), e=text.end(), c=b; + + while (b != e) + { + // Look for the next = + + for (c=b; c != e; c++) + if (*c == '=') + break; + + decodedString += string(b, c); + + if (c == e) // Got everything. + { + b=c; + break; + } + + b=c++; + + if (c == e) + break; + + if (*c == '\r') + c++; + + if (c == e) + break; + + if (*c == '\n') + { + c++; + b=c; + continue; + } + + int a1=xdigit(*c++); + + if (a1 < 0) + { + b=c; + continue; + } + + if (c == e) + break; + + int a2=xdigit(*c++); + + if (a2 < 0) + { + b=c; + continue; + } + + decodedString += (char)(a1 * 16 + a2); + + b=c; + } + + decodeBuffer=string(b, e); // Leftovers + + decoded(decodedString); +} + +#if 0 + +class testqp : public mail::decodeqp { + + void decoded(string buffer) + { + cout << buffer; + } +}; + +int main(int argc, char **argv) +{ + testqp qp; + + int argn; + + for (argn=1; argn < argc; argn++) + { + string s=argv[argn]; + + size_t p; + + while ((p=s.find('~')) != std::string::npos) + s[p]='\n'; + + qp.decode(s); + } + + cout << endl; + + return (0); +} +#endif diff --git a/libmail/qp.H b/libmail/qp.H new file mode 100644 index 0000000..fc83cbf --- /dev/null +++ b/libmail/qp.H @@ -0,0 +1,34 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_qp_H +#define libmail_qp_H + +#include "libmail_config.h" +#include "decoder.H" +#include "namespace.H" + +#include <string> + +LIBMAIL_START + +// +// MIME quoted-printable decoder + +class decodeqp : public decoder { + std::string decodeBuffer; + +public: + decodeqp(); + virtual ~decodeqp(); + + void decode(std::string text); +private: + virtual void decoded(std::string buffer)=0; +}; + +LIBMAIL_END + +#endif diff --git a/libmail/rfc2047decode.C b/libmail/rfc2047decode.C new file mode 100644 index 0000000..2853a9b --- /dev/null +++ b/libmail/rfc2047decode.C @@ -0,0 +1,80 @@ +/* +** Copyright 2002-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "rfc2047decode.H" +#include "rfc822/rfc822.h" +#include "rfc822/rfc2047.h" +#include "mail.H" +#include <cstring> + +using namespace std; + +mail::rfc2047::decoder::decoder() +{ +} + +mail::rfc2047::decoder::~decoder() +{ +} + +void mail::rfc2047::decoder::rfc2047_decode_callback(const char *text, + size_t text_len, + void *voidarg) +{ + ((mail::rfc2047::decoder *)voidarg)->rfc2047_callback(text, text_len); +} + +void mail::rfc2047::decoder::rfc2047_callback(const char *text, size_t text_len) +{ + decodedbuf.append(text, text+text_len); +} + +string mail::rfc2047::decoder::decode(string text, string charset) +{ + decodedbuf.clear(); + + if (rfc822_display_hdrvalue("subject", + text.c_str(), + charset.c_str(), + &mail::rfc2047::decoder:: + rfc2047_decode_callback, + NULL, + this) < 0) + return text; + + return decodedbuf; +} + +void mail::rfc2047::decoder::decode(vector<mail::address> &addr_cpy, + std::string charset) +{ + vector<mail::address>::iterator b=addr_cpy.begin(), e=addr_cpy.end(); + + while (b != e) + { + b->setName(decode(b->getName(), charset)); + b++; + } +} + +string mail::rfc2047::decoder::decodeSimple(string str) +{ + std::string output; + + if (rfc2047_decoder(str.c_str(), &decodeSimpleCallback, &output) < 0) + return str; + + return output; +} + +void mail::rfc2047::decoder::decodeSimpleCallback(const char *chset, + const char *lang, + const char *content, + size_t cnt, + void *dummy) +{ + ((std::string *)dummy)->append(content, content+cnt); +} diff --git a/libmail/rfc2047decode.H b/libmail/rfc2047decode.H new file mode 100644 index 0000000..6b6abb4 --- /dev/null +++ b/libmail/rfc2047decode.H @@ -0,0 +1,77 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_rfc2047_decode_h +#define libmail_rfc2047_decode_h + +#include "libmail_config.h" +#include "unicode/unicode.h" +#include "rfcaddr.H" + +#include <vector> +#include <string> + +#include "namespace.H" + +LIBMAIL_START + +// +// Mail header decoder. A variety of decoding options are available. +// This is basically a wrapper for librfc822.a's functions, with some +// value-added code. +// + +namespace rfc2047 { + + class decoder { + + std::string decodedbuf; + + public: + decoder(); + ~decoder(); + + private: + static void rfc2047_decode_callback(const char *text, + size_t text_len, + void *voidarg); + + void rfc2047_callback(const char *text, size_t text_len); + public: + + // Decode to unicode chars. + const unicode_char *decode(std::string rfc2047_text); + + // Decode to charset 'tocharset'. + + std::string decode(std::string rfc2047_text, + std::string charset); + + + // Decode the name portion in a parsed list of addresses. + // Decode to charset 'nativeInfo', and prepend [CHARSET] to + // content in any charset other than 'nativeInfo'. + + void decode(std::vector<mail::address> &addr_cpy, + std::string charset); + + // Decode without transcoding to a specific charset. + + static std::string decodeSimple(std::string str); + + private: + static void decodeSimpleCallback(const char *chset, + const char *lang, + const char *content, + size_t cnt, + void *dummy); + + }; +} + +LIBMAIL_END + +#endif + diff --git a/libmail/rfc2047encode.C b/libmail/rfc2047encode.C new file mode 100644 index 0000000..075efc3 --- /dev/null +++ b/libmail/rfc2047encode.C @@ -0,0 +1,57 @@ +/* +** Copyright 2002-2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "rfc2047encode.H" +#include "rfc822/rfc2047.h" +#include "mail.H" + +#include <cstdlib> + +mail::rfc2047::encode::encode(std::string txt, std::string charset) +{ + char *p=rfc2047_encode_str(txt.c_str(), charset.c_str(), + rfc2047_qp_allow_any); + + if (!p) + LIBMAIL_THROW("Out of memory."); + + try { + encodedString=p; + } catch (...) { + free(p); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + free(p); +} + +mail::rfc2047::encode::~encode() +{ +} + +mail::rfc2047::encodeAddrName::encodeAddrName(std::string txt, + std::string charset) + : encodedString(txt) +{ + char *p=rfc2047_encode_str(txt.c_str(), + charset.c_str(), + rfc2047_qp_allow_word); + + if (!p) + LIBMAIL_THROW("Out of memory."); + + try { + encodedString=p; + } catch (...) { + free(p); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + free(p); +} + +mail::rfc2047::encodeAddrName::~encodeAddrName() +{ +} diff --git a/libmail/rfc2047encode.H b/libmail/rfc2047encode.H new file mode 100644 index 0000000..518af78 --- /dev/null +++ b/libmail/rfc2047encode.H @@ -0,0 +1,52 @@ +/* +** Copyright 2002-2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_rfc2047_encode_h +#define libmail_rfc2047_encode_h + +#include <string> + +// +// Encode 8-bit header content. It's basically a C++ wrapper for +// rfc2047_encode_str in librfc822.a +// +// +// Usage: +// +// string hdr=mail::account::rfc2047::encode(hdr8bitContents, charset); +// + +#include "namespace.H" + +LIBMAIL_START + +namespace rfc2047 { + + class encode { + + std::string encodedString; + + public: + encode(std::string txt, std::string charset); + ~encode(); + + operator std::string() const { return encodedString; } + }; + + class encodeAddrName { + std::string encodedString; + public: + encodeAddrName(std::string txt, + std::string charset); + ~encodeAddrName(); + + operator std::string() const { return encodedString; } + }; +} + +LIBMAIL_END + +#endif + diff --git a/libmail/rfcaddr.C b/libmail/rfcaddr.C new file mode 100644 index 0000000..e91cbef --- /dev/null +++ b/libmail/rfcaddr.C @@ -0,0 +1,442 @@ +/* +** Copyright 2002-2011, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include <string.h> +#include "rfcaddr.H" +#include "mail.H" +#include "misc.H" +#include "rfc2047encode.H" +#include "rfc2047decode.H" +#include "rfc822/rfc822.h" +#include "unicode/unicode.h" + +#include <iostream> +#include <algorithm> + +#if LIBIDN +#include <idna.h> +#include <stringprep.h> + +namespace mail { + class libidn_init { + + public: + libidn_init(); + ~libidn_init(); + }; + + libidn_init libidn_init_instance; +} + +mail::libidn_init::libidn_init() +{ + if (!stringprep_check_version(STRINGPREP_VERSION)) + { + std::cerr << "Required stringprep version " + << STRINGPREP_VERSION << " is not installed" + << std::endl; + abort(); + } +} + +mail::libidn_init::~libidn_init() +{ +} +#endif + +using namespace std; + +mail::address::address(string n, string a): name(n), addr(a) +{ +} + +mail::address::~address() +{ +} + +string mail::address::toString() const +{ + if (addr.length() == 0) + return name; + + string q=addr; + + // Only add < > if there's a name + + if (name.length() > 0) + { + q=" <" + q + ">"; + + // If name is all alphabetics, don't bother with quotes. + + string::const_iterator b=name.begin(), e=name.end(); + + while (b != e) + { + if (!strchr(" ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789?*+-/=_", + *b)) + { + string s="\""; + + b=name.begin(), e=name.end(); + + while (b != e) + { + if (strchr("\\\"()<>", *b)) + s += "\\"; + // These chars must be escaped. + + s.append(&*b, 1); + b++; + } + return s + "\"" + q; + } + b++; + } + q=name + q; + } + return q; +} + +template<class T> +string mail::address::toString(string hdr, const vector<T> &h, + size_t w) +{ + int len=hdr.length(); + string comma=""; + bool first=true; + + string spc=""; + + spc.insert(spc.end(), len < 20 ? len:20, ' '); + // Leading whitespace for folded lines. + + typename std::vector<T>::const_iterator b=h.begin(), e=h.end(); + + while (b != e) + { + string s= b->toString(); + + if (!first && len + comma.length() + s.length() > w) + { + // Line too long, wrap it. + + if (comma.size() > 0) + comma=","; + + hdr=hdr + comma + "\n" + spc; + len=spc.size(); + comma=""; + } + first=false; + hdr = hdr + comma + s; + + len += comma.length() + s.length(); + comma=", "; + + if (b->addr.length() == 0) + comma=""; + b++; + } + return hdr; +} + +template +string mail::address::toString(string hdr, const vector<mail::address> &h, + size_t w); + +template +string mail::address::toString(string hdr, + const vector<mail::emailAddress> &h, + size_t w); + +string mail::address::getCanonAddress() const +{ + string a=addr; + + size_t n=a.find_last_of('@'); + + if (n < a.size()) + { + a=a.substr(0, n) + + mail::iconvert::convert_tocase(a.substr(n), + "iso-8859-1", + unicode_lc); + } + + return a; +} + +bool mail::address::operator==(const mail::address &o) const +{ + return getCanonAddress() == o.getCanonAddress(); +} + +static void parseCallback(const char *str, int pos, void *voidarg) +{ + *(size_t *)voidarg=pos; +} + +template<class T> bool mail::address::fromString(string addresses, + vector<T> &h, + size_t &errIndex) +{ + errIndex=std::string::npos; + + struct rfc822t *t=rfc822t_alloc_new(addresses.c_str(), + &parseCallback, &errIndex); + if (!t) + return false; + + struct rfc822a *a=rfc822a_alloc(t); + + if (!a) + { + rfc822t_free(t); + return false; + } + + try { + int i; + + for (i=0; i<a->naddrs; i++) + { + string name; + string addr; + + char *n=rfc822_display_name_tobuf(a, i, NULL); + + if (!n) + { + rfc822a_free(a); + rfc822t_free(t); + return false; + } + + try { + name=n; + free(n); + } catch (...) { + free(n); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + + n=rfc822_getaddr(a, i); + + if (!n) + { + rfc822a_free(a); + rfc822t_free(t); + return false; + } + + try { + addr=n; + free(n); + } catch (...) { + free(n); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + if (a->addrs[i].name == 0) + name=""; + + h.push_back( mail::address(name, addr)); + } + rfc822a_free(a); + rfc822t_free(t); + } catch (...) { + rfc822a_free(a); + rfc822t_free(t); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + return true; +} + +template +bool mail::address::fromString(string addresses, + vector<mail::address> &h, + size_t &errIndex); + +template +bool mail::address::fromString(string addresses, + vector<mail::emailAddress> &h, + size_t &errIndex); + + +void mail::address::setName(std::string s) +{ + name=s; +} + +void mail::address::setAddr(std::string s) +{ + addr=s; +} + +///////////////////////////////////////////////////////////////////////// + +mail::emailAddress::emailAddress() : mail::address("", "") +{ +} + +mail::emailAddress::~emailAddress() +{ +} + +mail::emailAddress::emailAddress(const mail::address &a) + : mail::address(a.getName(), a.getAddr()) +{ + decode(); +} + +std::string mail::emailAddress::setDisplayName(const std::string &s, + const std::string &charset) +{ + std::vector<unicode_char> ucbuf; + + if (!mail::iconvert::convert(s, charset, ucbuf)) + return "Encoding error"; + + decodedName=ucbuf; + + name=mail::rfc2047::encodeAddrName(s, charset); + + return ""; +} + +std::string mail::emailAddress::setDisplayAddr(const std::string &s, + const std::string &charset) +{ + std::vector<unicode_char> ucbuf; + + if (!mail::iconvert::convert(s, charset, ucbuf)) + return "Encoding error"; + +#if LIBIDN + std::vector<unicode_char>::iterator b, e; + + for (b=ucbuf.begin(), e=ucbuf.end(); b != e; ++b) + { + if (*b == '@') + { + ++b; + break; + } + } + + size_t addr_start= b-ucbuf.begin(); + + // Assume non-latin usernames are simply UTF-8 */ + + std::string name_portion= + mail::iconvert::convert(std::vector<unicode_char>(ucbuf.begin(), + b), + "utf-8"); + + ucbuf.push_back(0); + + char *ascii_ptr; + + int rc=idna_to_ascii_4z(&ucbuf[addr_start], &ascii_ptr, 0); + + if (rc != IDNA_SUCCESS) + return std::string(std::string("Address encoding error: ") + + idna_strerror((Idna_rc)rc)); + + try { + name_portion += ascii_ptr; + free(ascii_ptr); + } catch (...) { + free(ascii_ptr); + throw; + } + + ucbuf.pop_back(); + addr=name_portion; +#else + std::vector<unicode_char>::iterator b, e; + + for (b=ucbuf.begin(), e=ucbuf.end(); b != e; ++b) + { + if (*b < ' ' || *b > 0x7F) + return "Internationalized addresses not supported"; + } + + addr.clear(); + addr.reserve(ucbuf.size()); + addr.insert(addr.end(), ucbuf.begin(), ucbuf.end()); + +#endif + decodedAddr=ucbuf; + return ""; +} + +void mail::emailAddress::setName(string s) +{ + mail::address::setName(s); + decode(); +} + +void mail::emailAddress::setAddr(string s) +{ + mail::address::setAddr(s); + decode(); +} + +void mail::emailAddress::decode() +{ + std::vector<unicode_char> ucname, ucaddr; + + mail::iconvert::convert(mail::rfc2047::decoder().decode(name, + "utf-8"), + "utf-8", ucname); + + ucaddr.reserve(addr.size()); + + std::string::iterator b, e; + + for (std::string::iterator b=addr.begin(), e=addr.end(); b != e; ++b) + ucaddr.push_back((unsigned char)*b); + +#if LIBIDN + size_t at=std::find(ucaddr.begin(), ucaddr.end(), '@') + - ucaddr.begin(); + + if (at != std::string::npos) + { + ++at; + ucaddr.push_back(0); + + unicode_char *ucbuf; + + int rc=idna_to_unicode_4z4z(&ucaddr[at], &ucbuf, 0); + + if (rc == IDNA_SUCCESS) + { + size_t i; + + for (i=0; ucbuf[i]; ++i) + ; + + try { + ucaddr.erase(ucaddr.begin()+at, + ucaddr.end()); + ucaddr.insert(ucaddr.end(), + ucbuf, ucbuf+i); + free(ucbuf); + } catch (...) { + free(ucbuf); + throw; + } + } + } +#endif + decodedName=ucname; + decodedAddr=ucaddr; +} + diff --git a/libmail/rfcaddr.H b/libmail/rfcaddr.H new file mode 100644 index 0000000..45e2a45 --- /dev/null +++ b/libmail/rfcaddr.H @@ -0,0 +1,154 @@ +/* +** Copyright 2002-20011, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_rfcaddr_h +#define libmail_rfcaddr_h + +#include "libmail_config.h" +#include "unicode/unicode.h" +#include "namespace.H" + +#include <stdlib.h> +#include <vector> +#include <string> + +// +// An RFC 2822 address, a name and user@domain. +// + +LIBMAIL_START + +class address { + +protected: + std::string name; + std::string addr; +public: + + std::string getName() const { return name; } + std::string getAddr() const { return addr; } + + virtual void setName(std::string s); + virtual void setAddr(std::string a); + + // If addr.size() == 0 this is the obsolete rfc 822 + // group list notation, and name is either ";" or "name:" + + address(std::string n, std::string a); + + virtual ~address(); + + // Convert myself to "name" <addr> format. + + std::string toString() const; + + // + // Create an RFC 2822 address header, from a vector of addresss. + // + + template<class T> + static std::string toString(std::string hdr, // Name: + const std::vector<T> &h, + size_t w=76); // Max length of lines. + + // + // Create a vector of addresses from a string + // + + template<class T> static bool fromString(std::string addresses, + std::vector<T> &h, + size_t &errIndex); + + // Addresses are appended to h + // Returns false if memory allocation failed or addresses is + // syntactically invalid. errIndex points where in addresses the + // syntax error is (or is string::npos if memory allocation failed) + + std::string getCanonAddress() const; + // Return addr with domain portion converted to lowercase + + bool operator==(const address &) const; + // Compare the addr portions only, ignoring domain case + + bool operator!=(const address &a) const + { + return !operator==(a); + } + +}; + +// INPROGRESS: slowly migrate to automatic MIME encoding of names. +// +// A subclass of address that MIME-decodes the name portion of the address, +// and IDN-decodes the hostname portion of the address (if libidn is available). +// +// emailAddress transparently casts to mail::address, importing/exporting +// the name and address in its decoded form. + +class emailAddress : public address { + + // The name field as unicode characters + + std::vector<unicode_char> decodedName; + + //! The address field, with the hostname portion decoded + + std::vector<unicode_char> decodedAddr; + + void decode(); +public: + emailAddress(); + emailAddress(const address &); + virtual ~emailAddress(); + + std::string getDisplayName(const std::string &charset, + bool &errflag) const + { + return mail::iconvert::convert(decodedName, charset, errflag); + } + + std::string getDisplayAddr(const std::string &charset, + bool &errflag) const + { + return mail::iconvert::convert(decodedAddr, charset, errflag); + } + + std::string getDisplayName(const std::string &charset) const + { + bool dummy; + + return getDisplayName(charset, dummy); + } + + std::string getDisplayAddr(const std::string &charset) const + { + bool dummy; + + return getDisplayAddr(charset, dummy); + } + + std::string setDisplayName(const std::string &s, + const std::string &charset); + std::string setDisplayAddr(const std::string &a, + const std::string &charset); + + virtual void setName(std::string s); + virtual void setAddr(std::string s); + + bool operator==(const address &a) const + { + return mail::address::operator==(a); + } + + bool operator!=(const address &a) const + { + return !operator==(a); + } + +}; + +LIBMAIL_END + +#endif diff --git a/libmail/runlater.C b/libmail/runlater.C new file mode 100644 index 0000000..e206c3f --- /dev/null +++ b/libmail/runlater.C @@ -0,0 +1,49 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "runlater.H" +#if TIME_WITH_SYS_TIME +# include <sys/time.h> +# include <time.h> +#else +# if HAVE_SYS_TIME_H +# include <sys/time.h> +# else +# include <time.h> +# endif +#endif + +std::queue<mail::ptr<mail::runLater> > mail::runLater::runningLater; + +mail::runLater::runLater() +{ +} + +mail::runLater::~runLater() +{ +} + +void mail::runLater::RunLater() +{ + runningLater.push(this); +} + +void mail::runLater::checkRunLater(int &timeout) +{ + mail::runLater *ptr; + + while (!runningLater.empty()) + { + ptr=runningLater.front(); + runningLater.pop(); + + if (ptr) + { + timeout=0; + ptr->RunningLater(); + } + } +} diff --git a/libmail/runlater.H b/libmail/runlater.H new file mode 100644 index 0000000..995ac02 --- /dev/null +++ b/libmail/runlater.H @@ -0,0 +1,46 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_runlater_H +#define libmail_runlater_H + +#include "libmail_config.h" +#include "objectmonitor.H" + +#include <queue> + +/* +** Local mail drivers are immediate - they implement the request and invoke +** the callback function without delay. Some requests iterate over an entire +** message set (such as mail::copyMessages). Each callback function +** iterates to the next message. With large message sets the stack could +** grow quite big. The solution is the mail::runLater interface. After +** calling RunLater(), the caller MUST immediately terminate. The stack +** will unwind back to mail::account::process(), which then invokes the +** checkRunLater() method. +*/ + +LIBMAIL_START + +class runLater; + +class runLater : public mail::obj { +public: + runLater(); + ~runLater(); + + static std::queue<mail::ptr<runLater> > runningLater; + + void RunLater(); + + virtual void RunningLater()=0; + + static void checkRunLater(int &timeout); +}; + +LIBMAIL_END + +#endif + diff --git a/libmail/search.C b/libmail/search.C new file mode 100644 index 0000000..0895f2e --- /dev/null +++ b/libmail/search.C @@ -0,0 +1,1198 @@ +/* +** Copyright 2002-2011, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" + +#include "search.H" +#include "envelope.H" +#include "structure.H" +#include "rfcaddr.H" +#include "rfc2047decode.H" +#include "unicode/unicode.h" + +#include "rfc822/rfc822.h" +#include "rfc822/rfc822hdr.h" +#include "rfc822/rfc2047.h" + +#include <sstream> +#include <iomanip> +#include <errno.h> +#include <string.h> + +using namespace std; + +mail::searchParams::searchParams() : searchNot(false), criteria(replied), + scope(search_all) +{ +} + +mail::searchParams::~searchParams() +{ +} + +static const struct { + mail::searchParams::Criteria code; + const char *name; +} searchCriteriaTable[] = { + {mail::searchParams::replied, "replied"}, + {mail::searchParams::deleted, "deleted"}, + {mail::searchParams::draft, "draft"}, + {mail::searchParams::unread, "unread"}, + {mail::searchParams::from, "from"}, + {mail::searchParams::to, "to"}, + {mail::searchParams::cc, "cc"}, + {mail::searchParams::bcc, "bcc"}, + {mail::searchParams::subject, "subject"}, + {mail::searchParams::header, "header"}, + {mail::searchParams::body, "body"}, + {mail::searchParams::text, "text"}, + {mail::searchParams::before, "before"}, + {mail::searchParams::on, "on" }, + {mail::searchParams::since, "since"}, + {mail::searchParams::sentbefore, "sentbefore"}, + {mail::searchParams::senton, "senton"}, + {mail::searchParams::sentsince, "sentsince"}, + {mail::searchParams::larger, "larger"}, + {mail::searchParams::smaller, "smaller"}, + {mail::searchParams::replied, NULL}, +}; + +mail::searchParams::searchParams(string s) + : searchNot(false), criteria(replied), + scope(search_all), rangeLo(0), rangeHi(0) +{ + string::iterator b=s.begin(), c; + + if (b != s.end()) + switch (*b) { + case 'A': + scope=search_all; + break; + case 'M': + scope=search_marked; + break; + case 'U': + scope=search_unmarked; + default: + { + istringstream i(s); + char dummy; + + i >> rangeLo >> dummy >> rangeHi; + + if (!i.fail() && dummy == '-') + scope=search_range; + } + break; + } + + while (b != s.end() && *b != ' ') + b++; + + while (b != s.end() && *b == ' ') + b++; + + if (b != s.end() && *b == '!') + { + searchNot=true; + ++b; + } + + c=b; + while (c != s.end() && *c != ' ') + ++c; + + string n(b, c); + + size_t i; + + for (i=0; searchCriteriaTable[i].name; i++) + if (n == searchCriteriaTable[i].name) + { + criteria=searchCriteriaTable[i].code; + break; + } + + if (c != s.end()) + ++c; + + s=string(c, s.end()); + + s=decode(s, param1); + s=decode(s, param2); + decode(s, charset); +} + +string mail::searchParams::searchParams::decode(string s, string &v) +{ + ostringstream o; + + string::iterator b=s.begin(), e=s.end(); + + while (b != e) + { + if (*b == ' ') + { + ++b; + break; + } + + if (*b != '%') + { + o << (char)*b; + ++b; + continue; + } + + ++b; + + char hx[3]; + + if (b != e) + { + hx[0]=*b++; + if (b != e) + { + hx[1]=*b++; + hx[2]=0; + + istringstream i(hx); + + unsigned n; + + i >> hex >> n; + + if (!i.fail()) + o << (char)n; + } + } + } + + v=o.str(); + return string(b, e); +} + +mail::searchParams::operator std::string() const +{ + string s; + + size_t i; + + for (i=0; searchCriteriaTable[i].name; i++) + if (criteria == searchCriteriaTable[i].code) + { + s=searchCriteriaTable[i].name; + break; + } + + if (searchNot) + s = "!" +s; + + switch (scope) { + case search_all: + s= "A " + s; + break; + case search_marked: + s= "M " + s; + break; + case search_unmarked: + s = "U " + s; + break; + case search_range: + { + ostringstream o; + + o << rangeLo << '-' << rangeHi << ' '; + + s=o.str() + s; + } + } + + return s + " " + encode(param1) + " " + encode(param2) + " " + + encode(charset); +} + + +string mail::searchParams::encode(std::string s) +{ + ostringstream o; + + string::iterator b, e; + + b=s.begin(); + e=s.end(); + + while (b != e) + { + if (*b <= ' ' || *b >= 127 || *b == '%') + { + o << '%' << hex << setw(2) << setfill('0') << + (int)(unsigned char)*b; + } + else + o << (char)*b; + ++b; + } + + return o.str(); +} + + +/////////////////////////////////////////////////////////////////////////// + +int mail::searchOneMessage::Searcher::converted(const char *str, + size_t n) +{ + const unicode_char *uc=reinterpret_cast<const unicode_char *>(str); + n /= sizeof(unicode_char); + + while (n) + { + mail::Search::operator<<(*uc); + ++uc; + --n; + } + return 0; +} + +mail::searchCallback::searchCallback() +{ +} + +mail::searchCallback::~searchCallback() +{ +} + +mail::searchOneMessage::Callback::Callback() +{ +} + +mail::searchOneMessage::Callback::~Callback() +{ +} + +void mail::searchOneMessage::Callback::success(string message) +{ + (me->*nextFunc)(); +} + +void mail::searchOneMessage::Callback::fail(string message) +{ + me->callback.fail(message); + delete me; +} + +void mail::searchOneMessage +::Callback::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + me->callback.reportProgress(bytesCompleted, bytesEstimatedTotal, + me->alreadyCompleted, + me->alreadyCompleted+1); +} + +void mail::searchOneMessage::Callback +::messageEnvelopeCallback(size_t messageNumber, + const mail::envelope &envelope) +{ + me->search(envelope); +} + +void mail::searchOneMessage::Callback +::messageArrivalDateCallback(size_t messageNumber, + time_t datetime) +{ + me->search(datetime); +} + +void mail::searchOneMessage::Callback +::messageSizeCallback(size_t messageNumber, + unsigned long size) +{ + me->search(size); +} + +void mail::searchOneMessage::Callback +::messageStructureCallback(size_t messageNumber, + const mail::mimestruct &messageStructure) +{ + me->search(messageStructure); +} + +void mail::searchOneMessage::Callback +::messageTextCallback(size_t n, string text) +{ + me->search(text); +} + +mail::searchOneMessage::searchOneMessage(mail::searchCallback + &callbackArg, + mail::searchParams &searchInfoArg, + mail::account *ptrArg, + size_t messageNumArg, + size_t alreadyCompletedArg) + : callback(callbackArg), + searchInfo(searchInfoArg), + ptr(ptrArg), + messageNum(messageNumArg), + alreadyCompleted(alreadyCompletedArg) +{ + my_callback.me=this; + uid=ptr->getFolderIndexInfo(messageNum).uid; +} + +mail::searchOneMessage::~searchOneMessage() +{ +} + +void mail::searchOneMessage::go() +{ + char *c=libmail_u_convert_tocase(searchInfo.param2.c_str(), + searchInfo.charset.c_str(), + unicode_uc, + NULL); + + if (c == NULL) + { + my_callback.fail("Unknown search charset."); + return; + } + + try + { + searchInfo.param2=c; + free(c); + } catch (...) { + free(c); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + c=libmail_u_convert_tocase(searchInfo.param1.c_str(), + "iso-8859-1", + unicode_uc, + NULL); + if (!c) + { + my_callback.fail(strerror(errno)); + return; + } + + try { + searchInfo.param1=c; + free(c); + } catch (...) { + free(c); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + searchCharset=searchInfo.charset; + searchFlag=false; + + try { + vector<size_t> messageNumVector; + + messageNumVector.push_back(messageNum); + + size_t n; + + switch (searchInfo.criteria) { + case searchParams::larger: + case searchParams::smaller: + + my_callback.nextFunc= + &mail::searchOneMessage::checkNextHeader; + ptr->readMessageAttributes(messageNumVector, + mail::account::MESSAGESIZE, + my_callback); + return; + + case searchParams::before: + case searchParams::on: + case searchParams::since: + case searchParams::sentbefore: + case searchParams::senton: + case searchParams::sentsince: + + while (searchInfo.param2.size() > 0 && + unicode_isspace((unsigned char)searchInfo + .param2[0])) + searchInfo.param2=searchInfo.param2.substr(1); + + n=searchInfo.param2.find(' '); + + if (n != std::string::npos) + searchInfo.param2= + searchInfo.param2.substr(0,n); + + for (n=0; n<searchInfo.param2.size(); n++) + if (searchInfo.param2[n] == '-') + searchInfo.param2[n]=' '; + + searchInfo.param2 += " 00:00:00 -0000"; + + cmpDate=rfc822_parsedt(searchInfo.param2.c_str()); + + if (cmpDate == 0) + { + my_callback.fail("Invalid date specified"); + return; + } + + my_callback.nextFunc= + &mail::searchOneMessage::checkFwdEnvelope; + ptr->readMessageAttributes(messageNumVector, + + (searchInfo.criteria == + searchInfo.before || + searchInfo.criteria == + searchInfo.on || + searchInfo.criteria == + searchInfo.since ? + + mail::account::ARRIVALDATE: + mail::account::ENVELOPE), + my_callback); + return; + + case searchParams::from: + case searchParams::to: + case searchParams::cc: + case searchParams::bcc: + case searchParams::subject: + my_callback.nextFunc= + &mail::searchOneMessage::checkFwdEnvelope; + ptr->readMessageAttributes(messageNumVector, + mail::account::ENVELOPE, + my_callback); + return; + + case searchParams::header: + case searchParams::body: + case searchParams::text: + + while (!mimeSearch.empty()) + mimeSearch.pop(); + + headerSearchBuffer=""; + my_callback.nextFunc= + &mail::searchOneMessage::checkNextHeader; + ptr->readMessageAttributes(messageNumVector, + mail::account::MIMESTRUCTURE, + my_callback); + return; + + case searchParams::replied: + case searchParams::deleted: + case searchParams::draft: + case searchParams::unread: + break; + } + } catch (...) { + my_callback.fail("An exception occured in mail::searchOneMessage."); + return; + } + + checkSearch(); +} + +bool mail::searchOneMessage::sanityCheck() +{ + if (!ptr.isDestroyed() && messageNum < ptr->getFolderIndexSize() && + uid == ptr->getFolderIndexInfo(messageNum).uid) + return true; + checkSearch(); + return false; +} + +void mail::searchOneMessage::checkFwdEnvelope() +{ + if (!sanityCheck()) + return; + + if (searchFlag) + { + checkSearch(); // No need + return; + } + + try { + vector<size_t> messageNumVector; + + messageNumVector.push_back(messageNum); + + my_callback.nextFunc= + &mail::searchOneMessage::checkSearch; + ptr->readMessageAttributes(messageNumVector, + mail::account::MIMESTRUCTURE, + my_callback); + } catch (...) { + my_callback.fail("An exception occured in mail::searchOneMessage."); + return; + } +} + +void mail::searchOneMessage::checkNextHeader() +{ + if (searchInfo.criteria == searchParams::header && + headerSearchBuffer.size() > 0) + search("\n"); + + searchEngine.end(); + + if (searchEngine) + searchFlag=true; + + if (!sanityCheck()) + return; + + mail::mimestruct *hdrs; + std::string bodyCharset("iso-8859-1"); + + for (;;) { + + if (searchFlag || mimeSearch.empty()) + { + checkSearch(); + return; + } + + hdrs=mimeSearch.front(); + + mimeSearch.pop(); + + my_callback.nextFunc=&mail::searchOneMessage::checkNextHeader; + headerSearchBuffer=""; + + switch (searchInfo.criteria) { + case searchParams::header: + + if (!searchEngine.setString(searchInfo.param2, + searchCharset)) + { + my_callback.fail(strerror(errno)); + return; + } + bodyCharset="utf-8"; + break; + + case searchParams::body: + case searchParams::text: + + if (!searchEngine.setString(searchInfo.param2, + searchCharset)) + { + my_callback.fail(strerror(errno)); + return; + } + + if (hdrs->type == "TEXT" && + hdrs->type_parameters.exists("CHARSET")) + { + bodyCharset=hdrs->type_parameters + .get("CHARSET"); + } + break; + default: + checkSearch(); // Shouldn't happen. + return; + } + + if (searchEngine.getSearchLen() == 0) // Empty srch string + { + checkSearch(); + return; + } + break; + } + + searchEngine.begin(bodyCharset.c_str(), + libmail_u_ucs4_native); + + switch (searchInfo.criteria) { + case searchParams::header: + ptr->readMessageContent(messageNum, true, *hdrs, + mail::readHeadersFolded, + my_callback); + break; + case searchParams::body: + case searchParams::text: + ptr->readMessageContentDecoded(messageNum, true, *hdrs, + my_callback); + break; + default: + break; + } +} + +void mail::searchOneMessage::checkSearch() +{ + if (searchInfo.criteria == searchInfo.text && !searchFlag) + { + // Not found, look in the headers. + + searchInfo.criteria=searchInfo.header; + searchInfo.param1=""; + headerSearchBuffer=""; + searchFwdEnvelope(structureBuffer); + checkNextHeader(); + return; + } + + if (searchInfo.searchNot) + searchFlag= !searchFlag; + + success(callback, messageNum, searchFlag); +} + +void mail::searchOneMessage::success(mail::searchCallback &callback, + size_t n, bool result) +{ + delete this; + + try { + vector<size_t> msgSet; + + if (result) + msgSet.push_back(n); + + callback.success(msgSet); + } catch (...) { + my_callback.fail("An exception has occured processing search results."); + return; + } +} + +void mail::searchOneMessage::search(const mail::envelope &envelope) +{ + switch (searchInfo.criteria) { + case searchParams::from: + case searchParams::to: + case searchParams::cc: + case searchParams::bcc: + case searchParams::subject: + case searchParams::sentbefore: + case searchParams::senton: + case searchParams::sentsince: + + searchEnvelope(envelope); + break; + default: + break; + } +} + +void mail::searchOneMessage::searchEnvelope(const mail::envelope &envelope) +{ + string searchStr; + const char *header="Subject"; + + switch (searchInfo.criteria) { + case searchParams::from: + searchStr= mail::address::toString("", envelope.from); + header="From"; + break; + case searchParams::to: + searchStr= mail::address::toString("", envelope.to); + header="To"; + break; + case searchParams::cc: + searchStr= mail::address::toString("", envelope.cc); + header="Cc"; + break; + case searchParams::bcc: + searchStr= mail::address::toString("", envelope.bcc); + header="Bcc"; + break; + case searchParams::subject: + searchStr=envelope.subject; + break; + + case searchParams::sentbefore: + case searchParams::senton: + case searchParams::sentsince: + + doDateCmp(envelope.date); + return; + default: + return; + } + + char *q=rfc822_display_hdrvalue_tobuf(header, + searchStr.c_str(), + "utf-8", + NULL, NULL); + + if (!q) + return; + + if (!searchEngine.setString(searchInfo.param2, searchCharset)) + { + free(q); + return; + } + + if (searchEngine.getSearchLen() == 0) + { + free(q); + return; + } + + std::vector<unicode_char> uc; + + if (!mail::iconvert::convert(q, "utf-8", uc)) + { + free(q); + return; + } + free(q); + + for (std::vector<unicode_char>::iterator + b(uc.begin()), e(uc.end()); b != e; ++b) + searchEngine << *b; + + if (searchEngine) + searchFlag=true; +} + +static const char spaces[]=" \t\r\n"; + +void mail::searchOneMessage::search(time_t internaldate) +{ + switch (searchInfo.criteria) { + case searchParams::before: + case searchParams::on: + case searchParams::since: + doDateCmp(internaldate); + break; + default: + break; + } +} + +void mail::searchOneMessage::search(unsigned long messageSize) +{ + unsigned long b=0; + + istringstream i(searchInfo.param2.c_str()); + + i >> b; + + if (!i.fail()) + switch (searchInfo.criteria) { + case searchParams::larger: + if (messageSize > b) + searchFlag=true; + break; + case searchParams::smaller: + if (messageSize < b) + searchFlag=true; + break; + default: + break; + } +} + +void mail::searchOneMessage::search(const mail::mimestruct &structureInfo) +{ + switch (searchInfo.criteria) { + case searchParams::from: + case searchParams::to: + case searchParams::cc: + case searchParams::bcc: + case searchParams::subject: + case searchParams::header: + case searchParams::body: + case searchParams::text: + + structureBuffer=structureInfo; + + searchFwdEnvelope(structureBuffer); + break; + default: + break; + } +} + +// Search envelopes of attached messages. + +void mail::searchOneMessage::searchFwdEnvelope(mail::mimestruct &structureInfo) +{ + if (searchFlag) + return; + + if (searchInfo.criteria == searchInfo.header) + mimeSearch.push(&structureInfo); // Queue up header searches + else if (searchInfo.criteria == searchInfo.body || + searchInfo.criteria == searchInfo.text) + { + if (structureInfo.getNumChildren() == 0) + // Only leafs receive the content search. + mimeSearch.push(&structureInfo); + } + else if (structureInfo.messagerfc822()) + searchEnvelope( structureInfo.getEnvelope()); + + size_t n=structureInfo.getNumChildren(); + size_t i; + + for (i=0; i<n; i++) + searchFwdEnvelope(*structureInfo.getChild(i)); +} + +void mail::searchOneMessage::search(string text) +{ + if (searchFlag) + return; + + if (searchInfo.criteria == searchInfo.header) + { + text=headerSearchBuffer + text; + + size_t n; + + while (!searchFlag && (n=text.find('\n')) != std::string::npos) + { + searchEngine.reset(); + + string s=text.substr(0, n); + + text=text.substr(n+1); + + std::string headername; + + size_t colon=s.find(':'); + + if (colon != std::string::npos) + { + headername=s.substr(0, colon); + s=s.substr(colon+1); + } + + if (searchInfo.param1.size() > 0 && + rfc822hdr_namecmp(headername.c_str(), + searchInfo.param1.c_str())) + continue; + + char *value= + rfc822_display_hdrvalue_tobuf(headername + .c_str(), + s.c_str(), + "utf-8", + NULL, + NULL); + + if (!value) + continue; + + + unicode_char *uc; + size_t ucsize; + + if (libmail_u_convert_tou_tobuf(value, + strlen(value), + "utf-8", + &uc, + &ucsize, + NULL)) + { + free(value); + continue; + } + free(value); + + size_t n; + + for (n=0; n<ucsize; ++n) + { + searchEngine << uc[n]; + } + free(uc); + + if (searchEngine) + searchFlag=true; + } + if (!searchFlag) + headerSearchBuffer=text; + } + else if (searchInfo.criteria == searchInfo.body || + searchInfo.criteria == searchInfo.text) + { + searchEngine(text.c_str(), text.size()); + + if (searchEngine) + searchFlag=true; + } +} + +void mail::searchOneMessage::doDateCmp(time_t t) +{ + struct tm *tmptr=gmtime(&cmpDate); + + if (!tmptr) + return; + + int y=tmptr->tm_year; + int m=tmptr->tm_mon; + int d=tmptr->tm_mday; + + tmptr=localtime(&t); + + int diff=0; + + if (tmptr->tm_year > y) + diff=1; + else if (tmptr->tm_year < y) + diff= -1; + else if (tmptr->tm_mon > m) + diff=1; + else if (tmptr->tm_mon < m) + diff= -1; + else if (tmptr->tm_mday > d) + diff=1; + else if (tmptr->tm_mday < d) + diff= -1; + + switch (searchInfo.criteria) { + case searchParams::on: + case searchParams::senton: + if (diff == 0) + searchFlag=true; + break; + + case searchParams::before: + case searchParams::sentbefore: + + if (diff < 0) + searchFlag=true; + break; + + case searchParams::since: + case searchParams::sentsince: + + if (diff >= 0) + searchFlag=true; + break; + default: + break; + } +} + +/////////////////////////////////////////////////////////////////////// + +mail::searchMessages::searchMessages(mail::searchCallback + &callbackArg, + const mail::searchParams + &searchInfoArg, + mail::account *ptrArg) + : callback(callbackArg), server(ptrArg), searchInfo(searchInfoArg) +{ + search_callback.me=this; + + nextMsgNum=0; +} + +void mail::searchMessages::nextSearch() +{ + try { + RunLater(); + } catch (...) { + callback.fail("An exception occured in mail::searchMessages."); + } +} + +void mail::searchMessages::RunningLater() +{ + if (server.isDestroyed()) + { + callback.fail("Folder closed while a search was in progress."); + delete this; + return; + } + + size_t cnt=server->getFolderIndexSize(); + + while (nextMsgNum < cnt) + { + mail::messageInfo i=server->getFolderIndexInfo(nextMsgNum); + + switch (searchInfo.scope) { + case searchParams::search_all: + break; + + case searchParams::search_unmarked: + if (i.marked) + { + ++nextMsgNum; + continue; + } + break; + + case searchParams::search_marked: + if (!i.marked) + { + ++nextMsgNum; + continue; + } + break; + + case searchParams::search_range: + if (nextMsgNum < searchInfo.rangeLo + || nextMsgNum >= searchInfo.rangeHi) + { + ++nextMsgNum; + continue; + } + break; + } + + uid=i.uid; + + switch (searchInfo.criteria) { + case searchParams::replied: + if (searchInfo.searchNot ? !i.replied:i.replied) + successArray.push_back(uid); + ++nextMsgNum; + continue; + case searchParams::deleted: + if (searchInfo.searchNot ? !i.deleted:i.deleted) + successArray.push_back(uid); + ++nextMsgNum; + continue; + case searchParams::draft: + if (searchInfo.searchNot ? !i.draft:i.draft) + successArray.push_back(uid); + ++nextMsgNum; + continue; + case searchParams::unread: + if (searchInfo.searchNot ? !i.unread:i.unread) + successArray.push_back(uid); + ++nextMsgNum; + continue; + default: + break; + } + break; + } + + if (nextMsgNum >= cnt) + { + vector<size_t> msgNumArray; + + size_t n=server->getFolderIndexSize(); + + size_t i=0; + size_t j; + + for (j=0; j<n && i < successArray.size(); j++) + if (server->getFolderIndexInfo(j).uid + == successArray[i]) + { + msgNumArray.push_back(j); + i++; + } + + callback.success(msgNumArray); + delete this; + return; + } + + searchInfoCpy=searchInfo; + + mail::searchOneMessage *m=NULL; + + try { + m=new mail::searchOneMessage(search_callback, + searchInfoCpy, + server, + nextMsgNum, + nextMsgNum); + nextMsgNum++; + + if (!m) + { + callback.fail("Out of memory."); + delete m; + return; + } + + m->go(); + } catch (...) { + delete m; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +mail::searchMessages::~searchMessages() +{ +} + +mail::searchMessages::Callback::Callback() +{ +} + +mail::searchMessages::Callback::~Callback() +{ +} + +void mail::searchMessages::Callback::fail(string message) +{ + me->callback.fail(message); + delete me; +} + +void mail::searchMessages::Callback::success(const vector<size_t> &found) +{ + try { + if (found.size() > 0) + me->successArray.push_back(me->uid); + me->nextSearch(); + } catch (...) { + fail("Exception occured during a search."); + } +} + +void mail::searchMessages +::Callback::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + me->callback.reportProgress(bytesCompleted, bytesEstimatedTotal, + messagesCompleted, + me->server.isDestroyed() ? + messagesCompleted+1: + me->server->getFolderIndexSize()); +} + +void mail::searchMessages::search(mail::searchCallback + &callbackArg, + const mail::searchParams &searchInfoArg, + mail::account *ptrArg) +{ + mail::searchMessages *m=NULL; + + try { + m=new mail::searchMessages(callbackArg, searchInfoArg, + ptrArg); + + if (!m) + { + callbackArg.fail("Out of memory."); + return; + } + + m->nextSearch(); + } catch (...) { + if (m) + delete m; + callbackArg.fail("Exception occured trying to start a search."); + } +} + + diff --git a/libmail/search.H b/libmail/search.H new file mode 100644 index 0000000..e81326e --- /dev/null +++ b/libmail/search.H @@ -0,0 +1,290 @@ +/* +** Copyright 2002-2011, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_search_H +#define libmail_search_H + +#include "libmail_config.h" + +#if TIME_WITH_SYS_TIME +#include <sys/time.h> +#include <time.h> +#else +#if HAVE_SYS_TIME_H +#include <sys/time.h> +#else +#include <time.h> +#endif +#endif + +#include "mail.H" +#include "structure.H" +#include "runlater.H" + +#include "maildir/maildirsearch.h" + + +#include <vector> +#include <queue> + +#include <time.h> + +///////////////////////////////////////////////////////////////////////// +// +// Structure that describes the search criteria + +#include <string> + +LIBMAIL_START + +class searchParams { + +public: + searchParams(); + ~searchParams(); + + bool searchNot; // If set, the following criteria is negated + + enum Criteria { + + // Messages with the following flags set: + + replied, + deleted, + draft, + unread, + + // Header match + + from, + to, + cc, + bcc, + subject, + + header, + body, + text, + + // Internal date: + + before, + on, + since, + + // Sent date, + + sentbefore, + senton, + sentsince, + + larger, + smaller + } criteria; + + std::string param1; // Header name, or date, or byte size + std::string param2; // Header or content value + + std::string charset; // Charset for param2 + + // All strings use UTF-8 + + + enum Scope { + search_all, // Search all messages + + search_marked, // Only those msgs already marked + + search_unmarked, // Only those msgs not already marked + + search_range // Only messages in the following range: + } scope; + + size_t rangeLo, rangeHi; // Used for search_range only + + // Serialize/deserialize: + + searchParams(std::string); + operator std::string() const; + + // INTERNAL USE: + + static std::string decode(std::string, std::string &); + static std::string encode(std::string); +}; + +class searchCallback { +public: + searchCallback(); + virtual ~searchCallback(); + + virtual void success(const std::vector<size_t> &found)=0; + virtual void fail(std::string)=0; + + virtual void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal)=0; + +}; + +class searchOneMessage { + + searchCallback &callback; + +// +// For searches, mail::callback::success is called with a non-empty string +// if the message matches, an empty string if it does not. +// + + searchParams searchInfo; + time_t cmpDate; + + mail::ptr<mail::account> ptr; + size_t messageNum; + size_t alreadyCompleted; + std::string uid; + + std::string searchCharset; + + class Callback : public mail::callback::message { + public: + + searchOneMessage *me; + + void (searchOneMessage::*nextFunc)(); + + Callback(); + ~Callback(); + void success(std::string message); + void fail(std::string message); + + void messageEnvelopeCallback(size_t messageNumber, + const class envelope + &envelope); + + void messageArrivalDateCallback(size_t messageNumber, + time_t datetime); + + void messageSizeCallback(size_t messageNumber, + unsigned long size); + + void messageStructureCallback(size_t messageNumber, + const class mimestruct + &messageStructure); + void messageTextCallback(size_t n, std::string text); + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + }; + + Callback my_callback; + + bool searchFlag; + + class Searcher : public mail::Search, public mail::iconvert { + + int converted(const char *str, size_t n); + + public: + using mail::Search::operator bool; + using mail::Search::operator !; + using mail::iconvert::operator(); + }; + + Searcher searchEngine; + + mimestruct structureBuffer; + std::queue<mimestruct *> mimeSearch; + + std::string headerSearchBuffer; + + void doDateCmp(time_t); + +public: + + searchOneMessage(searchCallback &callbackArg, + searchParams &searchInfoArg, + mail::account *ptrArg, + size_t messageNumArg, + size_t alreadyCompletedArg=0); + + ~searchOneMessage(); + + void go(); + + void search(const envelope &envelope); + void search(time_t internaldate); + void search(unsigned long messageSize); + void search(const mimestruct &structureInfo); + void search(std::string text); + + void searchEnvelope(const envelope &envelope); + void searchFwdEnvelope(mimestruct &structureInfo); + +private: + bool sanityCheck(); + void checkSearch(); + void checkNextHeader(); + void checkFwdEnvelope(); + void success(searchCallback &callback, size_t messageNum, + bool result); +}; + +class searchMessages : public runLater { + + searchCallback &callback; + + size_t nextMsgNum; + + std::string uid; + + std::vector<std::string> successArray; + + class Callback : public searchCallback { + public: + searchMessages *me; + + Callback(); + ~Callback(); + + void fail(std::string message); + void success(const std::vector<size_t> &found); + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + } search_callback; + + ptr<mail::account> server; + + searchMessages(searchCallback &callbackArg, + const searchParams &searchInfoArg, + mail::account *ptrArg); + + ~searchMessages(); + + searchParams searchInfo, searchInfoCpy; + + void nextSearch(); + void RunningLater(); +public: + + friend class Callback; + + static void search(searchCallback &callbackArg, + const searchParams &searchInfoArg, + mail::account *ptrArg); +}; + +LIBMAIL_END + +#endif 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) +{ +} diff --git a/libmail/smap.H b/libmail/smap.H new file mode 100644 index 0000000..9a5a5de --- /dev/null +++ b/libmail/smap.H @@ -0,0 +1,86 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_smap_H +#define libmail_smap_H + +#include "libmail_config.h" +#include "mail.H" +#include "imap.H" + +#include <vector> + +LIBMAIL_START + +class smapHandler : public imapHandler { + +protected: + mail::callback *defaultCB; + + void timedOut(const char *errmsg); + + bool doDestroy; + +public: + smapHandler(int timeoutValArg=0); + virtual ~smapHandler(); + + int process(imap &imapAccount, std::string &buffer); + + int singleLineProcess(imap &imapAccount, std::string &buffer); + int multiLineProcessDotStuffed(imap &, std::string &); + int multiLineProcessBinary(imap &, std::string &); + + virtual bool processLine(imap &imapAccount, + std::vector<const char *> &words); + + virtual void beginProcessData(imap &imapAccount, + std::vector<const char *> &words, + unsigned long estimatedSize); + virtual void processData(imap &imapAccount, + std::string data); + virtual void endData(imap &imapAccount); + + virtual bool ok(std::string); + virtual bool fail(std::string); + + virtual void existsOrExpungeSeen(); + // Some subclasses want to know if an EXISTS or EXPUNGE was received + + virtual void fetchedMessageSize(size_t msgNum, + unsigned long bytes); + + virtual void fetchedInternalDate(size_t msgNum, + time_t internalDate); + virtual void fetchedIndexInfo(); + + virtual void messagesRemoved(std::vector< std::pair<size_t, size_t> > + &removedList); + virtual void messageChanged(size_t msgNum); + + static std::string words2path(std::vector<const char *> &); + + static void path2words(std::string, std::vector<std::string> &); + + static void commaSplit(std::string, std::vector<std::string> &); + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) + { + if (defaultCB) + defaultCB->reportProgress(bytesCompleted, + bytesEstimatedTotal, + messagesCompleted, + messagesEstimatedTotal); + } + +}; + +LIBMAIL_END + +#endif diff --git a/libmail/smapacl.C b/libmail/smapacl.C new file mode 100644 index 0000000..0dd59c9 --- /dev/null +++ b/libmail/smapacl.C @@ -0,0 +1,211 @@ +/* +** Copyright 2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "smap.H" +#include "smapacl.H" +#include <errno.h> +#include <stdio.h> + +using namespace std; + +//////////////////////////////////////////////////////////////////////// +// +// ACL + +const char *mail::smapACL::getName() +{ + return "ACL"; +} + +mail::smapACL::smapACL(string folderNameArg, + string &rightsArg, + mail::callback &getCallbackArg) + : folderName(folderNameArg), + rights(rightsArg) +{ + defaultCB= &getCallbackArg; +} + +mail::smapACL::~smapACL() +{ +} + +void mail::smapACL::installed(imap &imapAccount) +{ + vector<string> folderPathWords; + vector<string>::iterator b, e; + + string pstr=""; + + path2words(folderName, folderPathWords); + + b=folderPathWords.begin(); + e=folderPathWords.end(); + + while (b != e) + { + pstr += " "; + pstr += imapAccount.quoteSMAP( *b ); + b++; + } + + + imapAccount.imapcmd("", "ACL" + pstr + "\n"); +} + +bool mail::smapACL::processLine(imap &imapAccount, + vector<const char *> &words) +{ + if (words.size() >= 3 && strcmp(words[0], "*") == 0 && + strcasecmp(words[1], "ACL") == 0) + { + rights=words[2]; + return true; + } + + return smapHandler::processLine(imapAccount, words); +} + +//////////////////////////////////////////////////////////////////////// +// +// GETACL + +const char *mail::smapGETACL::getName() +{ + return "GETACL"; +} + +mail::smapGETACL::smapGETACL(string folderNameArg, + list<pair<string, string> > &rightsArg, + mail::callback &getCallbackArg) + : folderName(folderNameArg), + rights(rightsArg) +{ + defaultCB= &getCallbackArg; +} + +mail::smapGETACL::~smapGETACL() +{ +} + +void mail::smapGETACL::installed(imap &imapAccount) +{ + vector<string> folderPathWords; + vector<string>::iterator b, e; + + string pstr=""; + + path2words(folderName, folderPathWords); + + b=folderPathWords.begin(); + e=folderPathWords.end(); + + while (b != e) + { + pstr += " "; + pstr += imapAccount.quoteSMAP( *b ); + b++; + } + + + imapAccount.imapcmd("", "GETACL" + pstr + "\n"); +} + +bool mail::smapGETACL::processLine(imap &imapAccount, + vector<const char *> &words) +{ + if (words.size() >= 2 && strcmp(words[0], "*") == 0 && + strcasecmp(words[1], "GETACL") == 0) + { + vector<const char *>::iterator + b=words.begin()+2, e=words.end(); + + while (b != e) + { + string identifier= *b++; + + if (b != e) + { + string rightsW= *b++; + + rights.push_back(make_pair(identifier, + rightsW)); + } + } + return true; + } + + return smapHandler::processLine(imapAccount, words); +} + +//////////////////////////////////////////////////////////////////////// +// +// SETACL + +const char *mail::smapSETACL::getName() +{ + return "SETACL"; +} + +mail::smapSETACL::smapSETACL(string folderNameArg, + string identifierArg, + string rightsArg, + bool delIdentifierArg, + string &errorIdentifierArg, + vector<string> &errorRightsArg, + mail::callback &callbackArg) + : folderName(folderNameArg), + identifier(identifierArg), + rights(rightsArg), + delIdentifier(delIdentifierArg), + errorIdentifier(errorIdentifierArg), + errorRights(errorRightsArg) +{ + defaultCB= &callbackArg; +} + +mail::smapSETACL::~smapSETACL() +{ +} + +void mail::smapSETACL::installed(imap &imapAccount) +{ + vector<string> folderPathWords; + vector<string>::iterator b, e; + + string pstr=""; + + path2words(folderName, folderPathWords); + + b=folderPathWords.begin(); + e=folderPathWords.end(); + + while (b != e) + { + pstr += " "; + pstr += imapAccount.quoteSMAP( *b ); + b++; + } + + + imapAccount.imapcmd("", + (delIdentifier ? + "DELETEACL" + pstr + " \"\" " + + imapAccount.quoteSMAP(identifier): + "SETACL" + pstr + " \"\" " + + imapAccount.quoteSMAP(identifier) + + " " + + imapAccount.quoteSMAP(rights)) + "\n"); +} + +bool mail::smapSETACL::processLine(imap &imapAccount, + vector<const char *> &words) +{ + if (words.size() >= 2 && strcmp(words[0], "*") == 0 && + strcasecmp(words[1], "GETACL") == 0) + return true; // Don't care + + return smapHandler::processLine(imapAccount, words); +} diff --git a/libmail/smapacl.H b/libmail/smapacl.H new file mode 100644 index 0000000..790656c --- /dev/null +++ b/libmail/smapacl.H @@ -0,0 +1,80 @@ +/* +** Copyright 2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_smapacl_H +#define libmail_smapacl_H + +#include "libmail_config.h" +#include "mail.H" +#include "imap.H" +#include "smap.H" + +#include <vector> + +LIBMAIL_START + +class smapACL : public smapHandler { + + std::string folderName; + std::string &rights; + const char *getName(); + +public: + smapACL(std::string folderNameArg, + std::string &rightsArg, + mail::callback &getCallbackArg); + ~smapACL(); + void installed(imap &); + + bool processLine(imap &imapAccount, + std::vector<const char *> &words); +}; + +class smapGETACL : public smapHandler { + + std::string folderName; + std::list<std::pair<std::string, std::string> > &rights; + const char *getName(); + +public: + smapGETACL(std::string folderNameArg, + std::list<std::pair<std::string, std::string> > &rightsArg, + mail::callback &getCallbackArg); + ~smapGETACL(); + void installed(imap &); + + bool processLine(imap &imapAccount, + std::vector<const char *> &words); +}; + +class smapSETACL : public smapHandler { + + std::string folderName; + std::string identifier; + std::string rights; + bool delIdentifier; + std::string &errorIdentifier; + std::vector<std::string> &errorRights; + + const char *getName(); + +public: + smapSETACL(std::string folderName, + std::string identifierArg, + std::string rightsArg, + bool delIdentifierArg, + std::string &errorIdentifier, + std::vector<std::string> &errorRights, + mail::callback &callbackArg); + ~smapSETACL(); + void installed(imap &); + + bool processLine(imap &imapAccount, + std::vector<const char *> &words); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/smapadd.C b/libmail/smapadd.C new file mode 100644 index 0000000..07c3e7c --- /dev/null +++ b/libmail/smapadd.C @@ -0,0 +1,198 @@ +/* +** Copyright 2003-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "smapadd.H" +#include <errno.h> +#include <sstream> +#include <cstring> +#include <sys/types.h> +#include <sys/stat.h> + +using namespace std; + +const char* mail::smapAdd::getName() +{ + return "ADD"; +} + +/////////////////////////////////////////////////////////////////////////// +// +// The SMAP ADD command. + +mail::smapAdd::smapAdd(std::vector<std::string> cmdsArg, + mail::callback &callback) + : cmds(cmdsArg), + okfunc( &mail::smapAdd::sendNextCmd ), + failfunc( &mail::smapAdd::failCmd ), ready2send(false), tfile(NULL) +{ + defaultCB= &callback; +} + +mail::smapAdd::~smapAdd() +{ + if (tfile) + fclose(tfile); +} + +void mail::smapAdd::installed(imap &imapAccount) +{ + doDestroy=true; + sendNextCmd("OK"); + + if (doDestroy) + imapAccount.uninstallHandler(this); +} + +bool mail::smapAdd::ok(std::string msg) +{ + return (this->*okfunc)(msg); +} + +bool mail::smapAdd::fail(std::string msg) +{ + return (this->*failfunc)(msg); +} + +bool mail::smapAdd::sendNextCmd(std::string msg) +{ + if (cmds.size() == 0) + { + struct stat stat_buf; + + if (fstat(fileno(tfile), &stat_buf) < 0) + { + return failCmd(strerror(errno)); + } + + tfilebytecount=stat_buf.st_size; + + ostringstream o; + + o << "ADD {" << tfilebytecount << "/" << tfilebytecount + << "}\n"; + + okfunc=&mail::smapAdd::doneok; + doDestroy=false; + ready2send=true; + myimap->imapcmd("", o.str()); + return true; + } + + string s=cmds[0]; + + cmds.erase(cmds.begin(), cmds.begin()+1); + myimap->imapcmd("", s); + + doDestroy=false; + return true; +} + +bool mail::smapAdd::processLine(imap &imapAccount, + std::vector<const char *> &words) +{ + if (words.size() > 0 && ready2send && strncmp(words[0], ">", 1) == 0) + { + ready2send=false; + WriteTmpFile *w=new WriteTmpFile(this); + + if (!w) + LIBMAIL_THROW(strerror(errno)); + + w->tfile=tfile; + tfile=NULL; + w->tfilebytecount=tfilebytecount; + w->origbytecount=tfilebytecount; + + try { + imapAccount.socketWrite(w); + } catch (...) { + delete w; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + return true; + } + + if (words.size() >= 2 && strcmp(words[0], "*") == 0 && + strcasecmp(words[1], "ADD") == 0) + return true; + + return smapHandler::processLine(imapAccount, words); +} + +// When a command fails, must issue a RSET to clean up the ADD + +bool mail::smapAdd::failCmd(std::string msg) +{ + okfunc= &mail::smapAdd::donefail; + failfunc= &mail::smapAdd::donefail; + doDestroy=false; + finalmsg=msg; + myimap->imapcmd("", "RSET\n"); + return true; +} + +bool mail::smapAdd::doneok(std::string msg) +{ + return smapHandler::ok(finalmsg); +} + +bool mail::smapAdd::donefail(std::string msg) +{ + return smapHandler::fail(finalmsg); +} + +///////////////////////////////////////////// + +mail::smapAdd::WriteTmpFile::WriteTmpFile(smapAdd *myAddArg) + : myAdd(myAddArg), tfile(NULL), tfilebytecount(0), + origbytecount(0) +{ +} + +mail::smapAdd::WriteTmpFile::~WriteTmpFile() +{ + if (tfile) + fclose(tfile); +} + +bool mail::smapAdd::WriteTmpFile::fillWriteBuffer() +{ + if (!tfile) + return false; + + if (tfilebytecount == 0) + { + writebuffer += "\n"; + fclose(tfile); + tfile=NULL; + myAdd->reportProgress(origbytecount, + origbytecount, 1, 1); + return true; + } + + char buffer[BUFSIZ]; + + size_t n=sizeof(buffer); + + if (n > tfilebytecount) + n=tfilebytecount; + + int c=fread(buffer, 1, n, tfile); + + if (c <= 0) + { + memset(buffer, '\n', n); + c=(int)n; + } + + tfilebytecount -= c; + + writebuffer += string(buffer, buffer+c); + myAdd->reportProgress(origbytecount - tfilebytecount, + origbytecount, 0, 1); + myAdd->setTimeout(); + return true; +} diff --git a/libmail/smapadd.H b/libmail/smapadd.H new file mode 100644 index 0000000..378d6ca --- /dev/null +++ b/libmail/smapadd.H @@ -0,0 +1,72 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_smapadd_H +#define libmail_smapadd_H + +#include "libmail_config.h" +#include "mail.H" +#include "smap.H" + +#include <stdio.h> + +#include <vector> + +LIBMAIL_START + +class smapAdd : public smapHandler { + + const char *getName(); + + std::vector<std::string> cmds; + + bool (smapAdd::*okfunc)(std::string); + bool (smapAdd::*failfunc)(std::string); + + std::string finalmsg; + + unsigned long tfilebytecount; + + bool processLine(imap &imapAccount, + std::vector<const char *> &words); + + class WriteTmpFile : public mail::fd::WriteBuffer { + public: + smapAdd *myAdd; + FILE *tfile; + unsigned long tfilebytecount; + + unsigned long origbytecount; + + WriteTmpFile(smapAdd *myAddArg); + ~WriteTmpFile(); + + bool fillWriteBuffer(); + }; + + bool ready2send; + +public: + FILE *tfile; + smapAdd(std::vector<std::string> cmdsArg, + mail::callback &callback); + ~smapAdd(); + + void installed(imap &); + bool ok(std::string); + bool fail(std::string); + +private: + + bool sendNextCmd(std::string); + bool failCmd(std::string); + + bool doneok(std::string); + bool donefail(std::string); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/smapaddmessage.C b/libmail/smapaddmessage.C new file mode 100644 index 0000000..2224eb8 --- /dev/null +++ b/libmail/smapaddmessage.C @@ -0,0 +1,122 @@ +/* +** Copyright 2003-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "smapaddmessage.H" +#include "smapadd.H" +#include "rfc822/rfc822.h" +#include <errno.h> +#include <sstream> +#include <cstring> + +using namespace std; + +mail::smapAddMessage::smapAddMessage(mail::imap &imapAccountArg, + mail::callback &callbackArg) + : addMessage(&imapAccountArg), + imapAccount(imapAccountArg), + callback(&callbackArg), + tfile(tmpfile()) +{ +} + +mail::smapAddMessage::~smapAddMessage() +{ + if (tfile) + fclose(tfile); + if (callback) + callback->fail("Operation aborted."); +} + +void mail::smapAddMessage::saveMessageContents(std::string contents) +{ + size_t n; + + while ((n=contents.find('\r')) != std::string::npos) + contents.erase(contents.begin()+n, + contents.begin()+n+1); + + if (tfile) + if (fwrite(&contents[0], contents.size(), 1, tfile) != 1) + ; // Ignore gcc warning +} + +void mail::smapAddMessage::go() +{ + if (!tfile || ferror(tfile) || fflush(tfile) || + fseek(tfile, 0L, SEEK_SET) < 0) + { + fail(strerror(errno)); + return; + } + + if (!checkServer()) + return; + + { + string flags; + +#define FLAG +#define NOTFLAG ! +#define DOFLAG(NOT, field, name) \ + if (NOT messageInfo.field) flags += "," name + + LIBMAIL_SMAPFLAGS; +#undef DOFLAG +#undef NOTFLAG +#undef FLAG + + if (flags.size() > 0) + flags=flags.substr(1); + + ostringstream o; + + o << "ADD FLAGS=" << flags; + + if (messageDate) + { + char buf[80]; + + rfc822_mkdate_buf(messageDate, buf); + + o << " \"INTERNALDATE=" << buf << "\""; + } + + o << "\n"; + + cmds.push_back(o.str()); + } + + smapAdd *add=new smapAdd(cmds, *callback); + + if (add) + { + callback=NULL; + add->tfile=tfile; + tfile=NULL; + } + + try { + imapAccount.installForegroundTask(add); + } catch (...) { + if (add) + delete add; + delete this; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + delete this; +} + +void mail::smapAddMessage::fail(std::string errmsg) +{ + mail::callback *c=callback; + + callback=NULL; + delete this; + + if (c) + c->fail(errmsg); +} + diff --git a/libmail/smapaddmessage.H b/libmail/smapaddmessage.H new file mode 100644 index 0000000..2fd5361 --- /dev/null +++ b/libmail/smapaddmessage.H @@ -0,0 +1,42 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_smapaddmessage_H +#define libmail_smapaddmessage_H + +#include "libmail_config.h" +#include "mail.H" +#include "smap.H" + +#include "addmessage.H" +#include <stdio.h> + +#include <vector> + +LIBMAIL_START + +class smapAddMessage : public addMessage { + + mail::imap &imapAccount; + + mail::callback *callback; + + FILE *tfile; + +public: + smapAddMessage(mail::imap &imapAccount, + mail::callback &callbackArg); + ~smapAddMessage(); + + std::vector<std::string> cmds; + + void saveMessageContents(std::string); + void go(); + void fail(std::string); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/smapcopy.C b/libmail/smapcopy.C new file mode 100644 index 0000000..f2c1682 --- /dev/null +++ b/libmail/smapcopy.C @@ -0,0 +1,95 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ + +#include "smapcopy.H" + +#include <vector> +#include <sstream> + +using namespace std; + +const char *mail::smapCOPY::getName() +{ + return "COPY"; +} + +mail::smapCOPY::smapCOPY(const std::vector<size_t> &messages, + mail::folder *copyTo, + mail::imap &imapAccount, + mail::callback &callback, + const char *cmdArg) + : uidSet(imapAccount, messages), path(copyTo->getPath()), + cmd(cmdArg) +{ + defaultCB= &callback; +} + +mail::smapCOPY::~smapCOPY() +{ +} + +void mail::smapCOPY::installed(imap &imapAccount) +{ + msgRange.init(imapAccount, uidSet); + uidSet.clear(); + if (go()) + return; + + ok("OK"); + imapAccount.uninstallHandler(this); +} + +bool mail::smapCOPY::ok(std::string msg) +{ + if (go()) + { + doDestroy=false; + return true; + } + + return smapHandler::ok(msg); +} + +bool mail::smapCOPY::go() +{ + ostringstream msgList; + + msgList << cmd; + + if (!myimap->currentFolder || + myimap->currentFolder->closeInProgress || + !(msgRange >> msgList)) + return false; + + vector<string> words; + path2words(path, words); + + msgList << " \"\""; + + vector<string>::iterator fb=words.begin(), fe=words.end(); + + while (fb != fe) + { + msgList << " " << myimap->quoteSMAP( *fb ); + fb++; + } + + msgList << "\n"; + + myimap->imapcmd("", msgList.str()); + + return true; +} + +bool mail::smapCOPY::processLine(imap &imapAccount, + vector<const char *> &words) +{ + if (words.size() >= 2 && strcmp(words[0], "*") == 0 && + strcasecmp(words[1], "COPY") == 0) + return true; + + return smapHandler::processLine(imapAccount, words); +} diff --git a/libmail/smapcopy.H b/libmail/smapcopy.H new file mode 100644 index 0000000..4d42c3d --- /dev/null +++ b/libmail/smapcopy.H @@ -0,0 +1,46 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_smapcopy_H +#define libmail_smapcopy_H + +#include "libmail_config.h" +#include "mail.H" +#include "imap.H" +#include "smap.H" +#include "smapmsgrange.H" + +LIBMAIL_START + +class smapCOPY : public smapHandler { + + const char *getName(); + + smapUidSet uidSet; + smapMsgRange msgRange; + + std::string path; + + const char *cmd; + +public: + smapCOPY(const std::vector<size_t> &messages, + mail::folder *copyTo, + mail::imap &imapAccount, + mail::callback &callback, + const char *cmdArg); + + ~smapCOPY(); + + void installed(imap &); + bool ok(std::string); + + bool go(); + bool processLine(imap &imapAccount, std::vector<const char *> &words); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/smapcreate.C b/libmail/smapcreate.C new file mode 100644 index 0000000..98058a2 --- /dev/null +++ b/libmail/smapcreate.C @@ -0,0 +1,153 @@ +/* +** Copyright 2003-2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "smap.H" +#include "misc.H" +#include "smapcreate.H" +#include <errno.h> +#include <stdio.h> + +using namespace std; + +//////////////////////////////////////////////////////////////////////// +// +// CREATE + +const char *mail::smapCREATE::getName() +{ + return "CREATE"; +} + +mail::smapCREATE::smapCREATE(string pathArg, + string name, + bool createDirectoryArg, + mail::callback::folderList &listCallbackArg, + mail::callback &callbackArg) + + : path(pathArg), + createDirectory(createDirectoryArg), + renameFolder(false), + listCallback(&listCallbackArg) +{ + defaultCB= &callbackArg; + if (path.size() > 0) + path += "/"; + + name=toutf8(name); + + vector<const char *> w; + + w.push_back(name.c_str()); + + path += words2path(w); +} + +mail::smapCREATE::smapCREATE(string oldPathArg, + string pathArg, + string name, + mail::callback::folderList &listCallbackArg, + mail::callback &callbackArg) + : oldPath(oldPathArg), + path(pathArg), + createDirectory(false), + renameFolder(true), + listCallback(&listCallbackArg) +{ + defaultCB= &callbackArg; + if (path.size() > 0) + path += "/"; + + name=toutf8(name); + + vector<const char *> w; + + w.push_back(name.c_str()); + + path += words2path(w); +} + +mail::smapCREATE::smapCREATE(string pathArg, + bool createDirectoryArg, + mail::callback &callbackArg) + + : path(pathArg), + createDirectory(createDirectoryArg), + renameFolder(false), + listCallback(NULL) +{ + defaultCB= &callbackArg; +} + +mail::smapCREATE::~smapCREATE() +{ +} + +void mail::smapCREATE::installed(imap &imapAccount) +{ + vector<string> oldPathWords; + vector<string> words; + + path2words(oldPath, oldPathWords); + + path2words(path, words); + + vector<string>::iterator b, e; + + string pstr=""; + + if (renameFolder) + { + b=oldPathWords.begin(); + e=oldPathWords.end(); + + while (b != e) + { + pstr += " "; + pstr += imapAccount.quoteSMAP( *b ); + b++; + } + + pstr += " \"\""; + } + + b=words.begin(); + e=words.end(); + + while (b != e) + { + pstr += " "; + pstr += imapAccount.quoteSMAP( *b ); + b++; + } + + imapAccount.imapcmd("", (renameFolder ? "RENAME": + createDirectory ? "MKDIR":"CREATE") + + pstr + "\n"); +} + +bool mail::smapCREATE::ok(std::string okMsg) +{ + if (listCallback) + { + size_t n=path.rfind('/'); + + string name= n == std::string::npos ? path:path.substr(n+1); + + vector<string> words; + + path2words(name, words); + + mail::imapFolder f( *myimap, path, "", fromutf8(words[0]), 0); + + f.hasMessages(!createDirectory); + f.hasSubFolders(createDirectory); + + vector<const mail::folder *> a; + + a.push_back(&f); + listCallback->success(a); + } + return smapHandler::ok(okMsg); +} diff --git a/libmail/smapcreate.H b/libmail/smapcreate.H new file mode 100644 index 0000000..44fc1f6 --- /dev/null +++ b/libmail/smapcreate.H @@ -0,0 +1,54 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_smapcreate_H +#define libmail_smapcreate_H + +#include "libmail_config.h" +#include "mail.H" +#include "imap.H" +#include "smap.H" + +#include <vector> + +LIBMAIL_START + +class smapCREATE : public smapHandler { + + std::string oldPath; + std::string path; + bool createDirectory; + bool renameFolder; + mail::callback::folderList *listCallback; + + const char *getName(); + + +public: + smapCREATE(std::string pathArg, + std::string name, + bool createDirectoryArg, + mail::callback::folderList &listCallbackArg, + mail::callback &callbackArg); + + smapCREATE(std::string pathArg, + bool createDirectoryArg, + mail::callback &callbackArg); + + smapCREATE(std::string oldPathArg, + std::string pathArg, + std::string name, + mail::callback::folderList &listCallbackArg, + mail::callback &callbackArg); + + ~smapCREATE(); + void installed(imap &); + + bool ok(std::string); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/smapdelete.C b/libmail/smapdelete.C new file mode 100644 index 0000000..cc9018a --- /dev/null +++ b/libmail/smapdelete.C @@ -0,0 +1,52 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "smap.H" +#include "smapdelete.H" + +using namespace std; + +/////////////////////////////////////////////////////////////////////// +// +// DELETE + +const char *mail::smapDELETE::getName() +{ + return "DELETE"; +} + +mail::smapDELETE::smapDELETE(std::string pathArg, + bool deleteDirectoryArg, + mail::callback &callbackArg) + : path(pathArg), + deleteDirectory(deleteDirectoryArg) +{ + defaultCB= &callbackArg; +} + +mail::smapDELETE::~smapDELETE() +{ +} + +void mail::smapDELETE::installed(imap &imapAccount) +{ + vector<string> words; + + path2words(path, words); + + vector<string>::iterator b=words.begin(), e=words.end(); + + string pstr=""; + + while (b != e) + { + pstr += " "; + pstr += imapAccount.quoteSMAP( *b ); + b++; + } + + imapAccount.imapcmd("", (deleteDirectory ? "RMDIR":"DELETE") + + pstr + "\n"); +} diff --git a/libmail/smapdelete.H b/libmail/smapdelete.H new file mode 100644 index 0000000..ca447ab --- /dev/null +++ b/libmail/smapdelete.H @@ -0,0 +1,36 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_smapdelete_H +#define libmail_smapdelete_H + +#include "libmail_config.h" +#include "mail.H" +#include "imap.H" +#include "smap.H" + +#include <vector> + +LIBMAIL_START + +class smapDELETE : public smapHandler { + + std::string path; + bool deleteDirectory; + + const char *getName(); + +public: + smapDELETE(std::string pathArg, + bool deleteDirectoryArg, + mail::callback &callbackArg); + + ~smapDELETE(); + void installed(imap &); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/smapfetch.C b/libmail/smapfetch.C new file mode 100644 index 0000000..7f360b6 --- /dev/null +++ b/libmail/smapfetch.C @@ -0,0 +1,141 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ + +#include "smapfetch.H" + +using namespace std; + +const char *mail::smapFETCH::getName() +{ + return "FETCHMESSAGE"; +} + +mail::smapFETCH::smapFETCH(const std::vector<size_t> &messages, + bool peek, + std::string mime_id, + mail::readMode getType, + const char *decoded, + mail::callback::message &msgcallbackArg, + mail::imap &imapAccount) + : uidSet(imapAccount, messages), + msgcallback(msgcallbackArg), + expectedCount(messages.size()), + countProcessed(0) +{ + defaultCB= &msgcallbackArg; + + string element; + + switch (getType) { + case mail::readHeadersFolded: + element="HEADERS"; + break; + case mail::readContents: + element="BODY"; + break; + case mail::readBoth: + element="ALL"; + break; + case mail::readHeaders: + element="RAWHEADERS"; + break; + } + + fetchingMessageNum=0; + + if (decoded && *decoded) + element += decoded; + + if (mime_id.size() > 0) + mime_id="[" + mime_id + "]"; + + fetchCmd= mail::imap::quoteSMAP((peek ? "CONTENTS.PEEK=":"CONTENTS=") + + element + mime_id); +} + +mail::smapFETCH::~smapFETCH() +{ +} + +void mail::smapFETCH::installed(imap &imapAccount) +{ + msgRange.init(imapAccount, uidSet); + uidSet.clear(); + if (go()) + return; + + ok("OK"); + imapAccount.uninstallHandler(this); +} + +bool mail::smapFETCH::ok(std::string msg) +{ + if (go()) + { + doDestroy=false; + return true; + } + + return smapHandler::ok(msg); +} + +bool mail::smapFETCH::go() +{ + ostringstream msgList; + + msgList << "FETCH"; + + if (!myimap->currentFolder || + myimap->currentFolder->closeInProgress || + !(msgRange >> msgList)) + return false; + + msgList << " " << fetchCmd << "\n"; + myimap->imapcmd("", msgList.str()); + return true; +} + +void mail::smapFETCH::beginProcessData(imap &imapAccount, + std::vector<const char *> &words, + unsigned long estimatedSizeArg) +{ + estimatedSize=estimatedSizeArg; + sizeDone=0; + + if (words.size() >= 2 && strcasecmp(words[0], "FETCH") == 0) + { + string n=words[1]; + istringstream i(n); + + fetchingMessageNum=0; + + i >> fetchingMessageNum; + } +} + +void mail::smapFETCH::processData(imap &imapAccount, + std::string data) +{ + if (fetchingMessageNum > 0) + { + if ((sizeDone += data.size()) > estimatedSize) + estimatedSize=sizeDone; + + msgcallback.reportProgress(sizeDone, estimatedSize, + countProcessed, expectedCount); + msgcallback.messageTextCallback(fetchingMessageNum-1, + data); + } +} + +void mail::smapFETCH::endData(imap &imapAccount) +{ + if (++countProcessed > expectedCount) + expectedCount=countProcessed; + + msgcallback.reportProgress(sizeDone, sizeDone, countProcessed, + expectedCount); +} diff --git a/libmail/smapfetch.H b/libmail/smapfetch.H new file mode 100644 index 0000000..1c86074 --- /dev/null +++ b/libmail/smapfetch.H @@ -0,0 +1,62 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_smapfetch_H +#define libmail_smapfetch_H + +#include "libmail_config.h" +#include "mail.H" +#include "smap.H" + +#include "smapmsgrange.H" + +LIBMAIL_START + +class smapFETCH : public smapHandler { + + const char *getName(); + + smapUidSet uidSet; + smapMsgRange msgRange; + + std::string fetchCmd; + size_t fetchingMessageNum; + + mail::callback::message &msgcallback; + + size_t expectedCount; + size_t countProcessed; + + unsigned long estimatedSize; + unsigned long sizeDone; + +public: + smapFETCH(const std::vector<size_t> &messages, + bool peekArg, + std::string mime_idArg, + mail::readMode getType, + const char *decodedArg, + mail::callback::message &msgcallbackArg, + mail::imap &imapAccount); + ~smapFETCH(); + + void installed(imap &); + bool ok(std::string); + + bool go(); + +private: + void beginProcessData(imap &imapAccount, + std::vector<const char *> &words, + unsigned long estimatedSize); + void processData(imap &imapAccount, + std::string data); + void endData(imap &imapAccount); + +}; + +LIBMAIL_END + +#endif diff --git a/libmail/smapfetchattr.C b/libmail/smapfetchattr.C new file mode 100644 index 0000000..5124094 --- /dev/null +++ b/libmail/smapfetchattr.C @@ -0,0 +1,621 @@ +/* +** Copyright 2003-2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ + +#include "smap.H" +#include "misc.H" +#include "smapfetchattr.H" +#include "imapfolder.H" + +#include "rfcaddr.H" +#include "envelope.H" + +#include "rfc822/rfc822.h" + +#include <iostream> +#include <sstream> +#include <stdio.h> + +using namespace std; + +const char *mail::smapFETCHATTR::getName() +{ + return "FETCHATTR"; +} + +mail::smapFETCHATTR::smapFETCHATTR(const vector<size_t> &messages, + mail::account::MessageAttributes attributesArg, + mail::callback::message &callbackArg, + mail::imap &imapAccount) + : uidSet(imapAccount, messages), + attributes(attributesArg), msgcallback(callbackArg), + nextPtr(0) +{ + defaultCB= &msgcallback; +} + +mail::smapFETCHATTR::~smapFETCHATTR() +{ +} + +void mail::smapFETCHATTR::installed(imap &imapAccount) +{ + fetchList.init(imapAccount, uidSet); + uidSet.clear(); + if (go()) + { + return; + } + ok("OK"); + imapAccount.uninstallHandler(this); +} + + +bool mail::smapFETCHATTR::ok(string msg) +{ + if (doFetchingStructure) + { + checkMimeVersion(); + msgcallback.messageStructureCallback(fetchingMessageNum-1, + fetchingStructure); + } + + if (go()) + { + doDestroy=false; + return true; + } + + return smapHandler::ok(msg); +} + +bool mail::smapFETCHATTR::go() +{ + ostringstream msgList; + + doFetchingStructure=NULL; + fetchingMessageNum=0; + fetchingStructCount=0; + seenMimeVersion=false; + + if (!myimap->currentFolder || + myimap->currentFolder->closeInProgress || + !(fetchList >> msgList)) + { + return false; + } + + bool hasAttr=false; + + if (attributes & mail::account::ARRIVALDATE) + { + msgList << " INTERNALDATE"; + hasAttr=true; + } + + if (attributes & mail::account::MESSAGESIZE) + { + msgList << " SIZE"; + hasAttr=true; + } + + if (attributes & mail::account::MIMESTRUCTURE) + { + msgList << " CONTENTS.PEEK=MIME(:MIME)"; + hasAttr=true; + fetchingStructure=mail::mimestruct(); + fetchingStructure.type="TEXT"; + fetchingStructure.subtype="PLAIN"; + } + else /* MIMESTRUCTURE can also be used to build an envelope */ + + if (attributes & mail::account::ENVELOPE) + { + msgList << " CONTENTS.PEEK=HEADERS(:ENVELOPE)"; + hasAttr=true; + } + + if (!hasAttr) + return false; + + doFetchingEnvelope=false; + myimap->imapcmd("", "FETCH" + msgList.str() + "\n"); + return true; +} + +void mail::smapFETCHATTR::fetchedMessageSize(size_t msgNum, + unsigned long bytes) +{ + msgcallback.messageSizeCallback(msgNum, bytes); +} + +void mail::smapFETCHATTR::fetchedInternalDate(size_t msgNum, + time_t internalDate) +{ + msgcallback.messageArrivalDateCallback(msgNum, internalDate); +} + +// New FETCH data coming back. + +void mail::smapFETCHATTR::beginProcessData(imap &imapAccount, + vector<const char *> &words, + unsigned long estimatedSize) +{ + doFetchingEnvelope=false; + + if (words.size() >= 2 && strcasecmp(words[0], "FETCH") == 0 && + (attributes & (mail::account::ENVELOPE + |mail::account::MIMESTRUCTURE))) + { + size_t oldFetchingMessageNum=fetchingMessageNum; + + // Requested a FETCH for an envelope + + string n=words[1]; + istringstream i(n); + + fetchingMessageNum=0; + + i >> fetchingMessageNum; + + if (fetchingMessageNum > 0 && + fetchingMessageNum <= + (imapAccount.currentFolder && + !imapAccount.currentFolder->closeInProgress ? + imapAccount.currentFolder->exists:0)) + { + if (attributes & mail::account::MIMESTRUCTURE) + { + if (doFetchingStructure && + oldFetchingMessageNum + != fetchingMessageNum) + { + checkMimeVersion(); + msgcallback + .messageStructureCallback(oldFetchingMessageNum-1, + fetchingStructure); + fetchingStructure= mail::mimestruct(); + fetchingStructure.type="TEXT"; + fetchingStructure.subtype="PLAIN"; + } + + unsigned long mimeSize=0; + string mimeid=""; + string mimeparent=""; + + vector<const char *>::iterator + b=words.begin()+2, e=words.end(); + + while (b != e) + { + const char *c= *b++; + + if (strncasecmp(c, "SIZE=", 5) == 0) + { + string n=c+5; + + istringstream i(n); + + i >> mimeSize; + } + + if (strncasecmp(c, "MIME.ID=", 8) == 0) + mimeid=c+8; + + if (strncasecmp(c, "MIME.PARENT=", 12) + == 0) + { + mimeparent=c+12; + } + } + +#if 0 + cerr << "new MIME struct: id=" + << mimeid << ", parent=" << mimeparent + << endl; +#endif + + seenMimeVersion=false; + if (fetchingStructCount > 1000) + { + doFetchingStructure=NULL; + // Limit to 1000 MIME sections + } + else if (mimeid == "") + { + doFetchingStructure= + &fetchingStructure; + } + else + { + mail::mimestruct *p= + findMimeId(&fetchingStructure, + mimeparent, 0); + + doFetchingStructure=NULL; + if (p) + { + doFetchingStructure= + p->addChild(); + doFetchingStructure->type + ="TEXT"; + doFetchingStructure->subtype + ="PLAIN"; + } + } + + if (doFetchingStructure) + { + ++fetchingStructCount; + +#if 0 + cerr << "added " << mimeid + << ", parent's numChildren=" + << (doFetchingStructure + ->getParent() + ? doFetchingStructure + ->getParent()->getNumChildren() + : 0) << endl; +#endif + + doFetchingStructure->mime_id=mimeid; + doFetchingStructure->content_size= + mimeSize; + } + } + + doFetchingEnvelope=true; + fetchingEnvelope= mail::envelope(); + fetchingHeader=""; + } + } +} + +mail::mimestruct *mail::smapFETCHATTR::findMimeId(mail::mimestruct *p, + string mimeid, + size_t recursionLevel) +{ + if (!p || recursionLevel > 20) + return NULL; +#if 0 + cerr << "findMimeId(" << mimeid << "), current=" + << p->mime_id << endl; +#endif + if (p->mime_id == mimeid) + return p; + + size_t n=p->getNumChildren(); + size_t i; + + for (i=0; i<n; i++) + { + mail::mimestruct *q= findMimeId(p->getChild(i), mimeid, + recursionLevel+1); + + if (q) + return q; + } + + return NULL; +} + +void mail::smapFETCHATTR::checkMimeVersion() +{ + if (seenMimeVersion) + return; + + mail::mimestruct *p=doFetchingStructure->getParent(); + + if (p && !p->messagerfc822()) + return; // Assumed + + mail::mimestruct dummy; + + dummy.type="TEXT"; + dummy.subtype="PLAIN"; + + dummy.content_size= doFetchingStructure->content_size; + dummy.content_lines= doFetchingStructure->content_lines; + dummy.mime_id=doFetchingStructure->mime_id; + + *doFetchingStructure=dummy; +} + +// As header data comes back, split it at newlines, and parse each individual +// header line. + +void mail::smapFETCHATTR::processData(imap &imapAccount, + string data) +{ + if (!doFetchingEnvelope) + return; + + fetchingHeader += data; + + size_t n; + + while ((n=fetchingHeader.find('\n')) != std::string::npos) + { + string h=fetchingHeader.substr(0, n); + + fetchingHeader.erase(fetchingHeader.begin(), + fetchingHeader.begin()+n+1); + + processFetchedHeader(h); + } +} + +void mail::smapFETCHATTR::endData(imap &imapAccount) +{ + if (doFetchingEnvelope) + { + processFetchedHeader(fetchingHeader); // Anything that's left + + if (doFetchingStructure) + { + mail::mimestruct *p=doFetchingStructure->getParent(); + + if (p && p->messagerfc822()) + p->getEnvelope() = fetchingEnvelope; + } + + if ( (!doFetchingStructure || + doFetchingStructure->mime_id == "") && + attributes & mail::account::ENVELOPE) + msgcallback + .messageEnvelopeCallback(fetchingMessageNum-1, + fetchingEnvelope); + + } +} + +// Process a FETCHed header line. + +void mail::smapFETCHATTR::processFetchedHeader(string hdr) +{ + size_t n=hdr.find(':'); + + if (n == std::string::npos) + return; + + string h=hdr.substr(0, n); + + string::iterator b=hdr.begin()+n+1; + + while (b != hdr.end() && unicode_isspace((unsigned char)*b)) + b++; + + string v=string(b, hdr.end()); + + mail::upper(h); + if (doFetchingEnvelope) + { + if (h == "DATE") + { + fetchingEnvelope.date=rfc822_parsedt(v.c_str()); + } + + if (h == "SUBJECT") + { + fetchingEnvelope.subject=v; + } + + if (h == "IN-REPLY-TO") + { + fetchingEnvelope.inreplyto=v; + } + + if (h == "MESSAGE-ID") + { + fetchingEnvelope.messageid=v; + } + + if (h == "REFERENCES") + { + vector<address> address_list; + size_t err_index; + + address::fromString(v, address_list, err_index); + + fetchingEnvelope.references.clear(); + + vector<address>::iterator + b=address_list.begin(), + e=address_list.end(); + + while (b != e) + { + string s=b->getAddr(); + + if (s.size() > 0) + fetchingEnvelope.references + .push_back("<" + s + ">"); + ++b; + } + } + + if (h == "FROM") + { + size_t dummy; + + mail::address::fromString(v, fetchingEnvelope.from, + dummy); + } + + if (h == "TO") + { + size_t dummy; + + mail::address::fromString(v, fetchingEnvelope.to, + dummy); + } + + if (h == "CC") + { + size_t dummy; + + mail::address::fromString(v, fetchingEnvelope.cc, + dummy); + } + + if (h == "BCC") + { + size_t dummy; + + mail::address::fromString(v, fetchingEnvelope.bcc, + dummy); + } + + if (h == "SENDER") + { + size_t dummy; + + mail::address::fromString(v, fetchingEnvelope.sender, + dummy); + } + + if (h == "REPLY-TO") + { + size_t dummy; + + mail::address::fromString(v, fetchingEnvelope.replyto, + dummy); + } + + } + + if (doFetchingStructure) + { + if (h == "MIME-VERSION") + seenMimeVersion=1; + + if (h == "CONTENT-TYPE") + { + string t; + + parseMimeHeader(v, t, + doFetchingStructure->type_parameters); + + size_t n=t.find('/'); + + if (n == std::string::npos) + { + doFetchingStructure->type=t; + doFetchingStructure->subtype=""; + } + else + { + doFetchingStructure->type=t.substr(0, n); + doFetchingStructure->subtype=t.substr(n+1); + } + } + + if (h == "CONTENT-ID") + doFetchingStructure->content_id=v; + + if (h == "CONTENT-DESCRIPTION") + doFetchingStructure->content_description=v; + if (h == "CONTENT-TRANSFER-ENCODING") + doFetchingStructure->content_transfer_encoding=v; + if (h == "CONTENT-MD5") + doFetchingStructure->content_md5=v; + if (h == "CONTENT-LANGUAGE") + doFetchingStructure->content_language=v; + if (h == "CONTENT-DISPOSITION") + { + parseMimeHeader(v, doFetchingStructure-> + content_disposition, + doFetchingStructure-> + content_disposition_parameters); + } + } +} + +void mail::smapFETCHATTR::parseMimeHeader(std::string hdr, + std::string &name, + mail::mimestruct::parameterList &p) +{ + p=mail::mimestruct::parameterList(); + + string::iterator b=hdr.begin(), e=hdr.end(); + + name.clear(); + + while (b != e && *b != ';') + { + if (!unicode_isspace((unsigned char)*b)) + name += *b; + ++b; + } + + mail::upper(name); + + while (b != e) + { + if (unicode_isspace((unsigned char)*b) || *b == ';') + { + b++; + continue; + } + + string::iterator s=b; + + while (b != e) + { + if (*b == ';' || + unicode_isspace((unsigned char)*b) || *b == '=') + break; + ++b; + } + + string name(s, b), value; + + while (b != e && unicode_isspace((unsigned char)*b)) + ++b; + + if (b != e && *b == '=') + { + ++b; + + while (b != e && unicode_isspace((unsigned char)*b)) + ++b; + + bool inQuote=false; + + while (b != e) + { + if (!inQuote && (*b == ';' || + unicode_isspace(*b))) + { + b++; + break; + } + + if (*b == '"') + { + ++b; + inQuote= !inQuote; + continue; + } + + if (*b == '\\') + { + ++b; + if (b == e) + break; + } + + value += *b; + b++; + } + } + else + value="1"; + + mail::upper(name); + + p.set_simple(name, value); + } +} diff --git a/libmail/smapfetchattr.H b/libmail/smapfetchattr.H new file mode 100644 index 0000000..89ca22d --- /dev/null +++ b/libmail/smapfetchattr.H @@ -0,0 +1,83 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_smapfetchattr_H +#define libmail_smapfetchattr_H + +#include "libmail_config.h" +#include "mail.H" +#include "smap.H" +#include "envelope.H" +#include "structure.H" + +#include "smapmsgrange.H" + +LIBMAIL_START + +class smapFETCHATTR : public smapHandler { + + const char *getName(); + + smapUidSet uidSet; + smapMsgRange fetchList; + mail::account::MessageAttributes attributes; + mail::callback::message &msgcallback; + + size_t nextPtr; + +public: + smapFETCHATTR(const std::vector<size_t> &messages, + mail::account::MessageAttributes attributesArg, + mail::callback::message &callbackArg, + mail::imap &imapAccount); + ~smapFETCHATTR(); + + void installed(imap &); + bool ok(std::string); + + bool go(); + +private: + void fetchedMessageSize(size_t msgNum, + unsigned long bytes); + + void fetchedInternalDate(size_t msgNum, + time_t internalDate); + + void beginProcessData(imap &imapAccount, + std::vector<const char *> &words, + unsigned long estimatedSize); + void processData(imap &imapAccount, + std::string data); + void endData(imap &imapAccount); + + std::string fetchingHeader; + size_t fetchingMessageNum; + bool doFetchingEnvelope; + bool seenMimeVersion; + + mail::mimestruct *doFetchingStructure; + size_t fetchingStructCount; + + mail::envelope fetchingEnvelope; + mail::mimestruct fetchingStructure; + + void processFetchedHeader(std::string); + + void checkMimeVersion(); + + static mail::mimestruct *findMimeId(mail::mimestruct *p, + std::string mimeid, + size_t recursionLevel); + + static void parseMimeHeader(std::string, + std::string &, + mail::mimestruct::parameterList &); + +}; + +LIBMAIL_END + +#endif diff --git a/libmail/smapidle.C b/libmail/smapidle.C new file mode 100644 index 0000000..ebb4357 --- /dev/null +++ b/libmail/smapidle.C @@ -0,0 +1,184 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "smap.H" +#include "smapidle.H" +#include <iostream> + +using namespace std; + +mail::smapIdleHandler::smapIdleHandler(bool idleOnOffArg, + mail::callback *callbackArg) + : idleOnOff(idleOnOffArg), idling(false), + shouldTerminate(false), terminating(false) +{ + defaultCB=callbackArg; +} + +const char *mail::smapIdleHandler::getName() +{ + return "IDLE"; +} + +bool mail::smapIdleHandler::getTimeout(imap &imapAccount, + int &ioTimeout) +{ + if (waiting) + { + struct timeval tv; + + gettimeofday(&tv, NULL); + + if (tv.tv_sec > waitingUntil.tv_sec || + (tv.tv_sec == waitingUntil.tv_sec && + tv.tv_usec >= waitingUntil.tv_usec)) + { + waiting=false; + + imapAccount.imapcmd("", "IDLE\n"); + ioTimeout=0; + return false; + } + else + { + struct timeval t=waitingUntil; + + t.tv_usec -= tv.tv_usec; + + if (t.tv_usec < 0) + { + t.tv_usec += 1000000; + --t.tv_sec; + } + t.tv_sec -= tv.tv_sec; + + ioTimeout=t.tv_sec * 1000 + t.tv_usec / 1000; + + if (ioTimeout == 0) + ioTimeout=100; + return false; + } + } + + ioTimeout= 15 * 60 * 1000; + return false; +} + +void mail::smapIdleHandler::timedOut(const char *errmsg) +{ + mail::callback *c=defaultCB; + + defaultCB=NULL; + + if (c) + callbackTimedOut(*c, errmsg); +} + +mail::smapIdleHandler::~smapIdleHandler() +{ + mail::callback *c=defaultCB; + + defaultCB=NULL; + + if (c) + c->success("OK"); +} + +void mail::smapIdleHandler::installed(imap &imapAccount) +{ + if (!idleOnOff || !imapAccount.wantIdleMode + || shouldTerminate || !imapAccount.task_queue.empty()) + { + imapAccount.uninstallHandler(this); + return; + } + + // Wait 1/10th of a second before issuing an IDLE. When we're in + // IDLE mode, and a new task is started, we terminate and a requeue + // ourselves after the pending command. We don't want to immediately + // reenter the IDLE mode because if the first task queues up a second + // task we would have to immediately get out of IDLE mode right away, + // so wait 1/10th of a second to see if anything is up. + + gettimeofday(&waitingUntil, NULL); + if ((waitingUntil.tv_usec += 100000) > 1000000) + { + waitingUntil.tv_usec %= 1000000; + ++waitingUntil.tv_sec; + } + waiting=true; +} + +bool mail::smapIdleHandler::ok(std::string msg) +{ + if (!idling) // Response to the IDLE command + { + idling=true; + if (shouldTerminate) + terminateIdle(*myimap); + + mail::callback *c=defaultCB; + + defaultCB=NULL; + + if (c) + c->success("Monitoring folder for changes..."); + + doDestroy=false; + return true; + } + + // Response to the RESUME command + + mail::callback *c=defaultCB; + + defaultCB=NULL; + + if (myimap->wantIdleMode) + { + // Reinstall, when we're done + + myimap->installForegroundTask(new smapIdleHandler(true, + NULL)); + c=NULL; + } + + if (c) + c->success(msg); + + return true; +} + +void mail::smapIdleHandler::anotherHandlerInstalled(imap &imapAccount) +{ + if (waiting) + { + imapAccount.uninstallHandler(this); + return; + } + + shouldTerminate=true; + if (idling) + terminateIdle(imapAccount); +} + +void mail::smapIdleHandler::terminateIdle(imap &imapAccount) +{ + if (terminating) + return; + + terminating=true; + + mail::callback *c=defaultCB; + + defaultCB=NULL; + + if (c) + c->success("OK"); + + imapAccount.imapcmd("", "RESUME\n"); +} + diff --git a/libmail/smapidle.H b/libmail/smapidle.H new file mode 100644 index 0000000..6f9bc75 --- /dev/null +++ b/libmail/smapidle.H @@ -0,0 +1,52 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ + +#ifndef libmail_smapidle_H +#define libmail_smapidle_H + +#include "libmail_config.h" + +#include "mail.H" +#include "smap.H" + +#include <time.h> + +LIBMAIL_START + +class smapIdleHandler : public smapHandler { + + bool idleOnOff; + + bool idling; + bool shouldTerminate; + bool terminating; + + bool waiting; + struct timeval waitingUntil; + + void terminateIdle(imap &); + + const char *getName(); + void timedOut(const char *errmsg); + + + bool getTimeout(imap &, int &ioTimeout); + +public: + smapIdleHandler(bool idleOnOffArg, mail::callback *callbackArg); + + ~smapIdleHandler(); + + void installed(imap &); + + virtual bool ok(std::string); + + void anotherHandlerInstalled(imap &imapAccount); +}; + + +LIBMAIL_END +#endif diff --git a/libmail/smaplist.C b/libmail/smaplist.C new file mode 100644 index 0000000..6c620d3 --- /dev/null +++ b/libmail/smaplist.C @@ -0,0 +1,210 @@ +/* +** Copyright 2003-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "smap.H" +#include "misc.H" +#include "smaplist.H" +#include <sstream> +#include <errno.h> +#include <cstring> + +using namespace std; + +//////////////////////////////////////////////////////////////////////// +// +// LIST + +const char *mail::smapLIST::getName() +{ + return "LIST"; +} + +mail::smapLIST::smapLIST(string pathArg, + mail::callback::folderList &listCallbackArg, + mail::callback &callbackArg) + : path(pathArg), listCallback(listCallbackArg) +{ + defaultCB= &callbackArg; +} + +mail::smapLIST::~smapLIST() +{ + vector<mail::folder *>::iterator b=subfolders.begin(), + e=subfolders.end(); + + while (b != e) + { + mail::folder *f= *b; + + *b=NULL; + b++; + if (f) + delete f; + } +} + +void mail::smapLIST::installed(imap &imapAccount) +{ + string pstr=""; + + if (path.size() > 0) + { + vector<string> words; + + path2words(path, words); + + vector<string>::iterator b=words.begin(), e=words.end(); + + pstr=""; + + while (b != e) + { + pstr += " "; + pstr += imapAccount.quoteSMAP( *b ); + b++; + } + } + + imapAccount.imapcmd("", "LIST" + pstr + "\n"); +} + +bool mail::smapLIST::processLine(imap &imapAccount, + vector<const char *> &words) +{ + if (words.size() >= 4 && strcmp(words[0], "*") == 0 && + strcasecmp(words[1], "LIST") == 0) + { + vector<const char *> dummy; + + dummy.push_back(words[2]); + + string p=path; + if (p.size() > 0) + p += "/"; + p += words2path(dummy); + + imapFolder newFolder(imapAccount, p, "", + fromutf8(words[3]), 0); + + vector<const char *>::iterator b=words.begin() + 4; + + bool hasFOLDER=false; + bool hasDIRECTORY=false; + + while (b != words.end()) + { + const char *c= *b++; + + if (strcasecmp(c, "FOLDER") == 0) + { + hasFOLDER=true; + } + else if (strcasecmp(c, "DIRECTORY") == 0) + { + hasDIRECTORY=true; + } + } + + newFolder.hasMessages(hasFOLDER); + newFolder.hasSubFolders(hasDIRECTORY); + + imapFolder *f=new imapFolder(newFolder); + + if (!f) + LIBMAIL_THROW(strerror(errno)); + + try { + subfolders.push_back(f); + } catch (...) { + delete f; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + return true; + } + + return smapHandler::processLine(imapAccount, words); +} + +bool mail::smapLIST::ok(std::string okMsg) +{ + vector<const mail::folder *> cpy; + + cpy.insert(cpy.end(), subfolders.begin(), subfolders.end()); + + listCallback.success(cpy); + return smapHandler::ok(okMsg); +} + +mail::smapLISToneFolder +::smapLISToneFolder(std::string pathArg, + mail::callback::folderList &listCallbackArg, + mail::callback &callbackArg) + : smapLIST(pathArg, listCallbackArg, callbackArg) +{ + vector<string> words; + + path2words(path, words); + + nameComponent=words.end()[-1]; + size_t n=path.rfind('/'); + + if (n == std::string::npos) + path=""; + else + path=path.substr(0, n); +} + +mail::smapLISToneFolder::~smapLISToneFolder() +{ +} + +bool mail::smapLISToneFolder::processLine(imap &imapAccount, + std::vector<const char *> &words) +{ + if (words.size() >= 4 && strcmp(words[0], "*") == 0 && + strcasecmp(words[1], "LIST") == 0) + { + if (nameComponent != words[2]) + return true; + } + + if (!smapLIST::processLine(imapAccount, words)) + return false; + + if (subfolders.size() == 2) // Could be two entries, one a folder, + // then a second entry as a folder directory + { + mail::folder *a=subfolders[0]; + mail::folder *b=subfolders[1]; + + if (b->hasMessages()) + a->hasMessages(true); + + if (b->hasSubFolders()) + a->hasSubFolders(true); + + subfolders.erase(subfolders.begin() + 1); + delete b; + } + return true; +} + +bool mail::smapLISToneFolder::ok(std::string msg) +{ + // If the folder wasn't found, create a fake entry + + if (subfolders.size() == 0) + { + vector<const char *> dummy; + dummy.push_back("*"); + dummy.push_back("LIST"); + dummy.push_back(nameComponent.c_str()); + dummy.push_back(""); + processLine(*myimap, dummy); + } + + return smapLIST::ok(msg); +} + diff --git a/libmail/smaplist.H b/libmail/smaplist.H new file mode 100644 index 0000000..1cc10e6 --- /dev/null +++ b/libmail/smaplist.H @@ -0,0 +1,59 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_smaplist_H +#define libmail_smaplist_H + +#include "libmail_config.h" +#include "mail.H" +#include "imap.H" +#include "smap.H" + +#include <vector> + +LIBMAIL_START + +class smapLIST : public smapHandler { + +protected: + std::string path; + mail::callback::folderList &listCallback; + + const char *getName(); + +protected: + + std::vector<mail::folder *> subfolders; + +public: + smapLIST(std::string pathArg, + mail::callback::folderList &listCallbackArg, + mail::callback &callbackArg); + ~smapLIST(); + void installed(imap &); + + bool processLine(imap &imapAccount, + std::vector<const char *> &words); + bool ok(std::string); +}; + +class smapLISToneFolder : public smapLIST { + + std::string nameComponent; + +public: + smapLISToneFolder(std::string pathArg, + mail::callback::folderList &listCallbackArg, + mail::callback &callbackArg); + ~smapLISToneFolder(); + + bool processLine(imap &imapAccount, + std::vector<const char *> &words); + bool ok(std::string); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/smapmsgrange.C b/libmail/smapmsgrange.C new file mode 100644 index 0000000..4417341 --- /dev/null +++ b/libmail/smapmsgrange.C @@ -0,0 +1,140 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "smapmsgrange.H" +#include "imap.H" + +mail::smapUidSet::smapUidSet( mail::imap &imapAccount, + const std::vector<size_t> &v) +{ + if (imapAccount.currentFolder == NULL) + return; + + std::vector<size_t>::const_iterator b=v.begin(), e=v.end(); + + while (b != e) + { + size_t n= *b++; + + if (n >= imapAccount.currentFolder->exists) + continue; + + insert(imapAccount.currentFolder->index[n].uid); + } +} + +mail::smapUidSet::~smapUidSet() +{ +} + +bool mail::smapUidSet::getNextRange( mail::imap &imapAccount, + std::ostringstream &s) +{ + size_t i; + size_t n=imapAccount.currentFolder ? + imapAccount.currentFolder->exists:0; + + std::list< std::pair<size_t, size_t> > rangeList; + size_t rangeCount=0; + + for (i=0; i<n; i++) + { + std::set< std::string>::iterator pp= + find(imapAccount.currentFolder->index[i].uid); + + if (pp == end()) + continue; + + if (!rangeList.empty()) + { + std::list< std::pair<size_t, size_t> >::iterator + p= --rangeList.end(); + + if (p->second + 1 == i) + { + ++p->second; + erase(pp); + continue; + } + } + + if (++rangeCount > 100) + break; + + erase(pp); + rangeList.insert(rangeList.end(), std::make_pair(i, i)); + } + + if (rangeList.empty()) + return false; + + while (!rangeList.empty()) + { + std::list< std::pair<size_t, size_t> >::iterator + b=rangeList.begin(); + + s << " " << (b->first+1) << "-" << (b->second+1); + rangeList.pop_front(); + } + return true; +} + +mail::smapMsgRange::smapMsgRange() +{ +} + +void mail::smapMsgRange::init( mail::imap &imapAccount, + const mail::smapUidSet &uidSet) +{ + size_t i; + size_t n=imapAccount.currentFolder ? + imapAccount.currentFolder->exists:0; + + for (i=0; i<n; i++) + { + if (uidSet.count(imapAccount.currentFolder->index[i].uid) + == 0) + continue; + + if (!empty()) + { + mail::smapMsgRange::iterator p= --end(); + + if (p->second + 1 == i) + { + ++p->second; + continue; + } + } + + insert(end(), std::make_pair(i, i)); + } +} + +mail::smapMsgRange::~smapMsgRange() +{ +} + +bool mail::smapMsgRange::operator>>( std::ostringstream &s) +{ + bool flag=false; + int i; + + for (i=0; i<100; i++) + { + if (empty()) + break; + + flag=true; + + std::list< std::pair<size_t, size_t> >::iterator b=begin(); + + s << " " << (b->first+1) << "-" << (b->second+1); + + pop_front(); + } + + return flag; +} diff --git a/libmail/smapmsgrange.H b/libmail/smapmsgrange.H new file mode 100644 index 0000000..93f391f --- /dev/null +++ b/libmail/smapmsgrange.H @@ -0,0 +1,59 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_smapmsgrange_H +#define libmail_smapmsgrange_H + +#include "imap.H" +#include "imapfolder.H" +#include "namespace.H" + +#include <list> +#include <set> +#include <vector> +#include <sstream> + +LIBMAIL_START + +// +// Helper class for the helper class: after the time a request was made, +// and before it actually comes up in the scheduling queue, some messages +// might be expunged. Therefore, at first save everyone's UID, and +// then rebuild the message range from the UID list, when our turn comes +// up. + +class smapUidSet : public std::set< std::string> { +public: + smapUidSet( mail::imap &, const std::vector<size_t> &); + ~smapUidSet(); + + // Convert to smap message ranges on the fly + + bool getNextRange( mail::imap &imapAccount, + std::ostringstream &s); + +}; + +// +// Helper class: take a list of messages and convert it to smap message ranges +// + +class smapMsgRange : public std::list< std::pair<size_t, size_t> > { + +public: + smapMsgRange(); + + void init( mail::imap &, const smapUidSet &); + ~smapMsgRange(); + + bool operator>>( std::ostringstream &); + // Pop off up to hunnert ranges into a stringstream + // Returns false if no ranges are left to process. + +}; + +LIBMAIL_END + +#endif diff --git a/libmail/smapnewmail.C b/libmail/smapnewmail.C new file mode 100644 index 0000000..490c0a7 --- /dev/null +++ b/libmail/smapnewmail.C @@ -0,0 +1,110 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ + +#include "smap.H" +#include "imapfolder.H" +#include "smapopen.H" +#include "smapnewmail.H" +#include <string.h> +#include <sstream> + +using namespace std; + +const char *mail::smapNEWMAIL::getName() +{ + return "NEWMAIL"; +} + +mail::smapNEWMAIL::smapNEWMAIL(mail::callback *callbackArg, + bool isOpenArg) + : isOpen(isOpenArg), expectedFetchCount(0), fetchCount(0), + noopSent(false) +{ + defaultCB= callbackArg; +} + +mail::smapNEWMAIL::~smapNEWMAIL() +{ +} + +void mail::smapNEWMAIL::installed(imap &imapAccount) +{ + imapFOLDERinfo *p=myimap->currentFolder; + + if (p) + { + size_t n=p->index.size(); + + if (p->exists < n) + { + ostringstream o; + + o << "FETCH " << p->exists+1 << "-" << n + << " FLAGS UID\n" << flush; + + expectedFetchCount=(n - p->exists)*2; + + imapAccount.imapcmd("", o.str()); + return; + } + } + + smapHandler::ok("OK"); + imapAccount.uninstallHandler(this); +} + +// +// Report on our progress +// + +void mail::smapNEWMAIL::fetchedIndexInfo() +{ + ++fetchCount; + + if (fetchCount <= expectedFetchCount && defaultCB) + defaultCB->reportProgress(0, 0, + fetchCount/2, + expectedFetchCount/2); +} + +bool mail::smapNEWMAIL::ok(std::string str) +{ + if (!noopSent) + { + imapFOLDERinfo *p=myimap->currentFolder; + + while (p && p->exists < p->index.size() && + p->index[p->exists].uid.size() > 0) + ++p->exists; + + noopSent=true; + doDestroy=false; + myimap->imapcmd("", "NOOP\n"); // Get a snapshot + return true; + } + + if (myimap->currentFolder) + { + if (isOpen) + myimap->currentFolder->opened(); + else + myimap->currentFolder->folderCallback.newMessages(); + } + return smapHandler::ok(str); +} + +bool mail::smapNEWMAIL::fail(std::string str) +{ + if (myimap->currentFolder) + { + if (isOpen) + myimap->currentFolder->opened(); + else + myimap->currentFolder->folderCallback.newMessages(); + } + + return smapHandler::fail(str); +} diff --git a/libmail/smapnewmail.H b/libmail/smapnewmail.H new file mode 100644 index 0000000..2b7da9c --- /dev/null +++ b/libmail/smapnewmail.H @@ -0,0 +1,40 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_smapnewmail_H +#define libmail_smapnewmail_H + +#include "libmail_config.h" +#include "mail.H" +#include "smap.H" + +LIBMAIL_START + +class smapNEWMAIL : public smapHandler { + + const char *getName(); + + bool isOpen; + + size_t expectedFetchCount; + size_t fetchCount; + + bool noopSent; + + void fetchedIndexInfo(); +public: + smapNEWMAIL(mail::callback *callbackArg, + bool isOpenArg); + + void installed(imap &); + bool ok(std::string); + bool fail(std::string); + + ~smapNEWMAIL(); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/smapnoopexpunge.C b/libmail/smapnoopexpunge.C new file mode 100644 index 0000000..a4c9a48 --- /dev/null +++ b/libmail/smapnoopexpunge.C @@ -0,0 +1,124 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ + +#include "smap.H" +#include "smapnoopexpunge.H" +#include "imapfolder.H" +#include <sstream> + +using namespace std; + +const char *mail::smapNoopExpunge::getName() +{ + return cmd; +} + +mail::smapNoopExpunge::smapNoopExpunge(const char *cmdArg, + mail::callback &callbackArg, + mail::imap &imapAccount) + : cmd(cmdArg), uidSet( imapAccount, vector<size_t>()), + expungeSet(false), existsOrExpungeFlag(false) +{ + defaultCB= &callbackArg; +} + +mail::smapNoopExpunge::smapNoopExpunge(const vector<size_t> &messageList, + mail::callback &callbackArg, + mail::imap &imapAccount) + : cmd("EXPUNGE"), uidSet( imapAccount, messageList), + expungeSet(true), existsOrExpungeFlag(false) +{ + defaultCB= &callbackArg; +} + +void mail::smapNoopExpunge::existsOrExpungeSeen() +{ + existsOrExpungeFlag=true; +} + +mail::smapNoopExpunge::smapNoopExpunge(const char *cmdArg, + mail::imap &imapAccount) + : cmd(cmdArg), uidSet(imapAccount, vector<size_t>()), + expungeSet(false), existsOrExpungeFlag(false) +{ + defaultCB=NULL; +} + +mail::smapNoopExpunge::~smapNoopExpunge() +{ +} + +void mail::smapNoopExpunge::installed(imap &imapAccount) +{ + string imapCommand=cmd; + + if (expungeSet) + { + ostringstream o; + + if (!uidSet.getNextRange(imapAccount, o)) + { + mail::callback *p=defaultCB; + + defaultCB=NULL; + + imapAccount.uninstallHandler(this); + + if (p) + p->success("OK"); + return; + } + + imapCommand += o.str(); + } + imapAccount.imapcmd("", imapCommand + "\n"); +} + +bool mail::smapNoopExpunge::ok(std::string okmsg) +{ + if (expungeSet) + { + ostringstream o; // More? + + o << "EXPUNGE"; + + if (uidSet.getNextRange(*myimap, o)) + { + o << "\n"; + + myimap->imapcmd("", o.str()); + doDestroy=false; + return true; + } + } + + // If a NOOP/EXPUNGE found new messages, we don't want to invoke the + // callback function right away, because we must wait for + // smapNEWMAIL to finish populating the message index. + // Simply do this by requeueing another NOOP request at the end of + // the task queue. + // + if (existsOrExpungeFlag && defaultCB) + { + myimap->installForegroundTask(new smapNoopExpunge("NOOP", + *defaultCB, + *myimap) + ); + defaultCB=NULL; + } + + return smapHandler::ok(okmsg); +} + +bool mail::smapNoopExpunge::fail(std::string okmsg) +{ + + if (existsOrExpungeFlag) + myimap->installForegroundTask(new smapNoopExpunge("NOOP", + *myimap)); + + return smapHandler::fail(okmsg); +} diff --git a/libmail/smapnoopexpunge.H b/libmail/smapnoopexpunge.H new file mode 100644 index 0000000..610e3bd --- /dev/null +++ b/libmail/smapnoopexpunge.H @@ -0,0 +1,49 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_smapnoopexpunge_H +#define libmail_smapnoopexpunge_H + +#include "libmail_config.h" +#include "mail.H" +#include "smap.H" + +#include "smapmsgrange.H" + +LIBMAIL_START + +class smapNoopExpunge : public smapHandler { + + const char *cmd; + + smapUidSet uidSet; + bool expungeSet; + + const char *getName(); + + bool existsOrExpungeFlag; + + void existsOrExpungeSeen(); + +public: + smapNoopExpunge(const char *cmd, + mail::callback &callbackArg, + mail::imap &imapAccount); + smapNoopExpunge(const char *cmd, + mail::imap &imapAccount); + smapNoopExpunge(const std::vector<size_t> &messageList, + mail::callback &callbackArg, + mail::imap &imapAccount); + + void installed(imap &); + bool ok(std::string); + bool fail(std::string); + + ~smapNoopExpunge(); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/smapopen.C b/libmail/smapopen.C new file mode 100644 index 0000000..b41a932 --- /dev/null +++ b/libmail/smapopen.C @@ -0,0 +1,280 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ + +#include "smap.H" +#include "smapopen.H" +#include "smapnewmail.H" +#include "snapshot.H" +#include "imapfolder.H" +#include <string.h> +#include <errno.h> +#include <sstream> + +using namespace std; + +///////////////////////////////////////////////////////////////////////// +// +// Helper for restoring snapshots + + +class mail::smapOPEN::SnapshotRestoreHelper : public mail::snapshot::restore { + +public: + mail::imap &myimap; + mail::imapFOLDERinfo *myFolderInfo; + bool aborted; + + SnapshotRestoreHelper(mail::imapFOLDERinfo *myFolderInfoArg, + mail::imap &myimapArg); + ~SnapshotRestoreHelper(); + void restoreIndex(size_t msgNum, + const mail::messageInfo &info); + void restoreKeywords(size_t msgNum, + const std::set<std::string> &set); + void abortRestore(); +}; + +mail::smapOPEN::SnapshotRestoreHelper:: +SnapshotRestoreHelper(mail::imapFOLDERinfo *myFolderInfoArg, + mail::imap &myimapArg) + : myimap(myimapArg), myFolderInfo(myFolderInfoArg), aborted(false) +{ +} + +mail::smapOPEN::SnapshotRestoreHelper:: +~SnapshotRestoreHelper() +{ +} + +void mail::smapOPEN::SnapshotRestoreHelper:: +restoreIndex(size_t msgNum, + const mail::messageInfo &info) +{ + if (msgNum < myFolderInfo->index.size()) + ((mail::messageInfo &)myFolderInfo->index[msgNum])=info; +} + +void mail::smapOPEN::SnapshotRestoreHelper:: +restoreKeywords(size_t msgNum, + const set<string> &kset) +{ + if (msgNum < myFolderInfo->index.size()) + { + myFolderInfo->index[msgNum].keywords + .setFlags(myimap.keywordHashtable, + kset); + } +} + +void mail::smapOPEN::SnapshotRestoreHelper::abortRestore() +{ + aborted=true; +} + +///////////////////////////////////////////////////////////////////////// + +const char *mail::smapOPEN::getName() +{ + return "OPEN"; +} + +mail::smapOPEN::smapOPEN(string pathArg, + mail::snapshot *restoreSnapshotArg, + mail::callback &openCallbackArg, + mail::callback::folder &folderCallbackArg) + : path(pathArg), restoreSnapshot(restoreSnapshotArg), + folderCallback(folderCallbackArg), newSmapFolder(NULL), exists(0), + restoringSnapshot(false) +{ + defaultCB= &openCallbackArg; +} + +mail::smapOPEN::~smapOPEN() +{ + if (newSmapFolder) + delete newSmapFolder; +} + +void mail::smapOPEN::installed(imap &imapAccount) +{ + newSmapFolder=new smapFOLDER(path, folderCallback, imapAccount); + + if (!newSmapFolder) + LIBMAIL_THROW((strerror(errno))); + + vector<string> words; + + path2words(path, words); + + vector<string>::iterator b=words.begin(), e=words.end(); + + string pstr=""; + + while (b != e) + { + pstr += " "; + pstr += imapAccount.quoteSMAP( *b ); + b++; + } + + if (imapAccount.currentFolder) + imapAccount.currentFolder->closeInProgress=true; + + string snapshotId; + + if (restoreSnapshot) + { + size_t nMessages; + + restoreSnapshot->getSnapshotInfo(snapshotId, nMessages); + + if (snapshotId.size() > 0) + { + newSmapFolder->index.resize(nMessages); + + SnapshotRestoreHelper myHelper(newSmapFolder, + imapAccount); + restoreSnapshot->restoreSnapshot(myHelper); + + if (myHelper.aborted) + snapshotId=""; + } + + vector<imapFOLDERinfo::indexInfo>::iterator + b=newSmapFolder->index.begin(), + e=newSmapFolder->index.end(); + + while (b != e) + { + if (b->uid.size() == 0) + { + snapshotId=""; // Bad restore. + break; + } + b++; + } + + if (snapshotId.size() == 0) + newSmapFolder->index.clear(); + + imapAccount.imapcmd("", "SOPEN " + + mail::imap::quoteSMAP(snapshotId) + + " " + pstr + "\n"); + } + else + imapAccount.imapcmd("", "OPEN" + pstr + "\n"); +} + +bool mail::smapOPEN::processLine(imap &imapAccount, + std::vector<const char *> &words) +{ + if (words.size() >= 3 && strcmp(words[0], "*") == 0 && + strcasecmp(words[1], "SNAPSHOTEXISTS") == 0) + { + restoringSnapshot=true; + presnapshotExists=exists=newSmapFolder->index.size(); + return true; + } + + if (words.size() >= 3 && strcmp(words[0], "*") == 0 && + strcasecmp(words[1], "EXISTS") == 0) + { + string n(words[2]); + istringstream i(n); + i >> exists; + + if (restoringSnapshot && exists > newSmapFolder->index.size()) + newSmapFolder->index.resize(exists); + return true; + } + + if (restoringSnapshot) + { + imapFOLDERinfo *saveCurrentFolder= + imapAccount.currentFolder; + + bool rc; + + imapAccount.currentFolder=newSmapFolder; + + try { + rc=smapHandler::processLine(imapAccount, words); + } catch (...) { + imapAccount.currentFolder=saveCurrentFolder; + throw; + } + imapAccount.currentFolder=saveCurrentFolder; + return rc; + } + + return smapHandler::processLine(imapAccount, words); +} + +void mail::smapOPEN::messagesRemoved(vector< pair<size_t, size_t> > + &removedList) +{ + // Restoring a SNAPSHOT + + vector< pair<size_t, size_t> >::iterator + b=removedList.begin(), + e=removedList.end(); + + while (b != e) + { + --e; + + size_t i=e->first; + size_t j=e->second+1; + + if (exists >= j) + exists -= j - i; + else if (exists > i) + exists=i; + + if (restoringSnapshot) + { + if (presnapshotExists >= j) + presnapshotExists -= j - i; + else if (presnapshotExists > i) + presnapshotExists=i; + } + } +} + +void mail::smapOPEN::messageChanged(size_t msgNum) +{ +} + +bool mail::smapOPEN::ok(std::string s) +{ + smapFOLDER *f=newSmapFolder; + + newSmapFolder=NULL; + + myimap->currentFolder=f; + myimap->installBackgroundTask(f); + + if (!restoringSnapshot) + { + f->exists=0; + f->index.clear(); + } + else + { + f->exists=presnapshotExists; + } + + if (exists > (restoringSnapshot ? presnapshotExists:0)) + { + f->index.resize(exists); + myimap->installForegroundTask(new smapNEWMAIL(defaultCB, + true)); + defaultCB=NULL; + } + + return smapHandler::ok(s); +} + diff --git a/libmail/smapopen.H b/libmail/smapopen.H new file mode 100644 index 0000000..2edf04f --- /dev/null +++ b/libmail/smapopen.H @@ -0,0 +1,56 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_smapopen_H +#define libmail_smapopen_H + +#include "libmail_config.h" +#include "mail.H" +#include "imap.H" +#include "smap.H" + +#include <vector> + +LIBMAIL_START + +class smapFOLDER; + +class smapOPEN : public smapHandler { + + std::string path; + mail::snapshot *restoreSnapshot; + mail::callback::folder &folderCallback; + + smapFOLDER *newSmapFolder; + + const char *getName(); + + size_t exists; + + bool restoringSnapshot; + size_t presnapshotExists; + + class SnapshotRestoreHelper; + +public: + smapOPEN(std::string pathArg, + mail::snapshot *restoreSnapshotArg, + mail::callback &openCallbackArg, + mail::callback::folder &folderCallbackArg); + + ~smapOPEN(); + void installed(imap &); + bool processLine(imap &imapAccount, + std::vector<const char *> &words); + bool ok(std::string); + + void messagesRemoved(std::vector< std::pair<size_t, size_t> > + &removedList); + void messageChanged(size_t msgNum); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/smapsearch.C b/libmail/smapsearch.C new file mode 100644 index 0000000..65afe23 --- /dev/null +++ b/libmail/smapsearch.C @@ -0,0 +1,239 @@ +/* +** Copyright 2003-2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ + +#include "smapsearch.H" +#include "misc.H" +#include <sstream> + +using namespace std; + +const char *mail::smapSEARCH::getName() +{ + return "SEARCH"; +} + +mail::smapSEARCH::smapSEARCH(const searchParams ¶metersArg, + searchCallback &callbackArg, + mail::imap &imapAccount) + + : parameters(parametersArg), + callback(&callbackArg) +{ +} + +mail::smapSEARCH::~smapSEARCH() +{ + if (callback) + { + searchCallback *c=callback; + + callback=NULL; + c->fail("Operation aborted"); + } +} + +void mail::smapSEARCH::installed(imap &imapAccount) +{ + ostringstream o; + + switch (parameters.scope) { + case searchParams::search_all: + o << "SEARCH ALL"; + break; + + case searchParams::search_unmarked: + o << "SEARCH UNMARKED"; + break; + + case searchParams::search_marked: + o << "SEARCH MARKED"; + break; + case searchParams::search_range: + o << "SEARCH " << parameters.rangeLo+1 + << "-" << parameters.rangeHi; + break; + } + + bool searchNot=parameters.searchNot; + searchParams::Criteria criteria=parameters.criteria; + + string param1=parameters.param1; + string param2=mail::toutf8(parameters.param2); + + string::iterator p; + + for (p=param1.begin(); p != param1.end(); p++) + if (*p == '\n') + *p=' '; + + for (p=param2.begin(); p != param2.end(); p++) + if (*p == '\n') + *p=' '; + + if (criteria == mail::searchParams::unread) + searchNot= !searchNot; + + if (searchNot) + o << " NOT"; + + bool doParam1=false; + bool doParam2=false; + + switch (criteria) { + case mail::searchParams::replied: + o << " REPLIED\n"; + break; + case mail::searchParams::deleted: + o << " DELETED\n"; + break; + case mail::searchParams::draft: + o << " DRAFT\n"; + break; + case mail::searchParams::unread: + o << " SEEN\n"; + break; + case mail::searchParams::from: + o << " FROM "; + doParam2=true; + break; + case mail::searchParams::to: + o << " TO "; + doParam2=true; + break; + case mail::searchParams::cc: + o << " CC "; + doParam2=true; + break; + case mail::searchParams::bcc: + o << " BCC "; + doParam2=true; + break; + case mail::searchParams::subject: + o << " SUBJECT "; + doParam2=true; + break; + case mail::searchParams::header: + o << " HEADER "; + doParam1=true; + doParam2=true; + break; + case mail::searchParams::body: + o << " BODY "; + doParam2=true; + break; + case mail::searchParams::text: + o << " TEXT "; + doParam2=true; + break; + case mail::searchParams::before: + o << " BEFORE "; + doParam2=true; + break; + case mail::searchParams::on: + o << " ON "; + doParam2=true; + break; + case mail::searchParams::since: + o << " SINCE "; + doParam2=true; + break; + case mail::searchParams::sentbefore: + o << " SENTBEFORE "; + doParam2=true; + break; + case mail::searchParams::senton: + o << " SENTON "; + doParam2=true; + break; + case mail::searchParams::sentsince: + o << " SENTSINCE "; + doParam2=true; + break; + case mail::searchParams::larger: + o << " LARGER "; + doParam2=true; + break; + case mail::searchParams::smaller: + o << " SMALLER "; + doParam2=true; + break; + } + + if (doParam1) + o << mail::imap::quoteSMAP(param1) << " "; + if (doParam2) + o << mail::imap::quoteSMAP(param2) << "\n"; + + imapAccount.imapcmd("", o.str()); +} + +bool mail::smapSEARCH::ok(std::string msg) +{ + searchCallback *c=callback; + + callback=NULL; + + if (c) + c->success(found); + + return true; +} + + +bool mail::smapSEARCH::fail(std::string msg) +{ + searchCallback *c=callback; + + callback=NULL; + + if (c) + c->fail(msg); + + return true; +} + +bool mail::smapSEARCH::processLine(imap &imapAccount, + vector<const char *> &words) +{ + if (words.size() >= 2 && + strcmp(words[0], "*") == 0 && + strcasecmp(words[1], "SEARCH") == 0) + { + vector<const char *>::iterator b=words.begin()+2, + e=words.end(); + + while (b != e) + { + size_t first=0; + size_t last=0; + char dummy=0; + + string w(*b++); + istringstream i(w); + + i >> first >> dummy >> last; + + if (dummy != '-') + last=first; + + if (first == 0 || last == 0 || last < first) + continue; + --first; + --last; + + do + { + if (first >= + (imapAccount.currentFolder ? + imapAccount.currentFolder->exists:0)) + break; + found.push_back(first); + } while (first++ < last); + } + return true; + } + return smapHandler::processLine(imapAccount, words); +} diff --git a/libmail/smapsearch.H b/libmail/smapsearch.H new file mode 100644 index 0000000..b0db84e --- /dev/null +++ b/libmail/smapsearch.H @@ -0,0 +1,45 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_smapsearch_H +#define libmail_smapsearch_H + +#include "libmail_config.h" +#include "mail.H" +#include "imap.H" +#include "smap.H" +#include "smapmsgrange.H" +#include "search.H" + +#include <vector> + +LIBMAIL_START + +class smapSEARCH : public smapHandler { + + const char *getName(); + + searchParams parameters; + searchCallback *callback; + + std::vector<size_t> found; + +public: + smapSEARCH(const searchParams ¶metersArg, + searchCallback &callbackArg, + mail::imap &imapAccount); + + ~smapSEARCH(); + + void installed(imap &); + bool ok(std::string); + bool fail(std::string); + + bool processLine(imap &imapAccount, std::vector<const char *> &words); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/smapsendfolder.C b/libmail/smapsendfolder.C new file mode 100644 index 0000000..10d6915 --- /dev/null +++ b/libmail/smapsendfolder.C @@ -0,0 +1,222 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "smapsendfolder.H" +#include "smapaddmessage.H" +#include <errno.h> +#include <cstring> + +using namespace std; + +mail::smapSendFolder::smapSendFolder(mail::imap *imap, + const smtpInfo &sendInfoArg, + std::string sentPathArg) + : folder(imap), imapAccount(imap), sendInfo(sendInfoArg), + sentPath(sentPathArg) +{ +} + +mail::smapSendFolder::~smapSendFolder() +{ +} + +void mail::smapSendFolder::sameServerAsHelperFunc() const +{ +} + +string mail::smapSendFolder::getName() const +{ + return ""; +} + +string mail::smapSendFolder::getPath() const +{ + return ""; +} + +bool mail::smapSendFolder::hasMessages() const +{ + return true; +} + +bool mail::smapSendFolder::hasSubFolders() const +{ + return false; +} + +bool mail::smapSendFolder::isParentOf(string path) const +{ + return false; +} + +void mail::smapSendFolder::hasMessages(bool arg) +{ +} + +void mail::smapSendFolder::hasSubFolders(bool arg) +{ +} + +void mail::smapSendFolder::readFolderInfo( callback::folderInfo + &callback1, + callback &callback2) const +{ + callback1.success(); + callback2.success("OK"); +} + +void mail::smapSendFolder::getParentFolder(callback::folderList &callback1, + callback &callback2) const +{ + callback2.fail("Not implemented"); +} + +void mail::smapSendFolder::readSubFolders( callback::folderList &callback1, + callback &callback2) const +{ + callback2.fail("Not implemented"); +} + + +mail::addMessage *mail::smapSendFolder::addMessage(callback &callback) const +{ + if (isDestroyed(callback)) + return NULL; + + mail::smapAddMessage *add=new smapAddMessage(*imapAccount, callback); + + if (!add) + { + callback.fail(strerror(errno)); + return NULL; + } + + try { + if (sentPath.find('\n') != std::string::npos || + sendInfo.sender.find('\n') != std::string::npos) + { + mail::smapAddMessage *p=add; + + add=NULL; + + p->fail("Invalid sender address."); + return NULL; + } + + if (sentPath.size() > 0) + mail::imap::addSmapFolderCmd(add, sentPath); + + add->cmds.push_back("ADD " + + mail::imap::quoteSMAP("MAILFROM=" + + sendInfo.sender) + + "\n"); + vector<string>::const_iterator b=sendInfo.recipients.begin(), + e=sendInfo.recipients.end(); + + while (b != e) + { + if ( (*b).find('\n') != std::string::npos) + { + mail::smapAddMessage *p=add; + + add=NULL; + + p->fail("Invalid recipient address."); + return NULL; + } + + add->cmds.push_back("ADD " + + mail::imap::quoteSMAP("RCPTTO=" + + *b) + + "\n"); + b++; + } + + map<string, string>::const_iterator optPtr; + + for (optPtr=sendInfo.options.begin(); + optPtr != sendInfo.options.end(); optPtr++) + { + + if (optPtr->second.find('\n') != + std::string::npos) + { + mail::smapAddMessage *p=add; + + add=NULL; + p->fail("Invalid option."); + return NULL; + } + } + + optPtr=sendInfo.options.find("DSN"); + + if (optPtr != sendInfo.options.end()) + { + add->cmds.push_back("ADD " + + mail::imap::quoteSMAP("NOTIFY=" + + optPtr + ->second) + + "\n"); + } + } catch (...) { + if (add) + { + delete add; + add=NULL; + } + } + return add; +} + +void mail::smapSendFolder::createSubFolder(string name, bool isDirectory, + callback::folderList + &callback1, + callback &callback2) const +{ + callback2.fail("Not implemented"); +} + + +void mail::smapSendFolder::create(bool isDirectory, + callback &callback) const +{ + callback.fail("Not implemented"); +} + +void mail::smapSendFolder::destroy(callback &callback, bool destroyDir) const +{ + callback.fail("Not implemented"); +} + +void mail::smapSendFolder::renameFolder(const folder *newParent, string newName, + callback::folderList &callback1, + callback &callback2) const +{ + callback2.fail("Not implemented"); +} + +mail::folder *mail::smapSendFolder::clone() const +{ + if (isDestroyed()) + { + errno=ENOENT; + return NULL; + } + + return new smapSendFolder(imapAccount, sendInfo, sentPath); +} + +string mail::smapSendFolder::toString() const +{ + return ""; +} + +void mail::smapSendFolder::open(callback &openCallback, + snapshot *restoreSnapshot, + callback::folder &folderCallback) const +{ + openCallback.fail("Not implemented"); +} diff --git a/libmail/smapsendfolder.H b/libmail/smapsendfolder.H new file mode 100644 index 0000000..74219c0 --- /dev/null +++ b/libmail/smapsendfolder.H @@ -0,0 +1,69 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_smapsendfolder_H +#define libmail_smapsendfolder_H + +#include "libmail_config.h" +#include "mail.H" +#include "imap.H" +#include "smtpinfo.H" + +LIBMAIL_START + +class smapSendFolder : public folder { + + mail::imap *imapAccount; + + smtpInfo sendInfo; + std::string sentPath; + +public: + smapSendFolder(mail::imap *, const smtpInfo &, std::string); + ~smapSendFolder(); + + void sameServerAsHelperFunc() const; + + std::string getName() const; + + std::string getPath() const; + bool hasMessages() const; + bool hasSubFolders() const; + bool isParentOf(std::string path) const; + void hasMessages(bool); + void hasSubFolders(bool); + + void readFolderInfo( callback::folderInfo + &callback1, + callback &callback2) const; + void getParentFolder(callback::folderList &callback1, + callback &callback2) const; + void readSubFolders( callback::folderList &callback1, + callback &callback2) const; + + mail::addMessage *addMessage(callback &callback) const; + + void createSubFolder(std::string name, bool isDirectory, + callback::folderList + &callback1, + callback &callback2) const; + + void create(bool isDirectory, + callback &callback) const; + void destroy(callback &callback, bool destroyDir) const; + + void renameFolder(const folder *newParent, std::string newName, + callback::folderList &callback1, + callback &callback2) const; + folder *clone() const; + std::string toString() const; + void open(callback &openCallback, + snapshot *restoreSnapshot, + callback::folder &folderCallback) const; +}; + +LIBMAIL_END + +#endif diff --git a/libmail/smapstatus.C b/libmail/smapstatus.C new file mode 100644 index 0000000..f441a03 --- /dev/null +++ b/libmail/smapstatus.C @@ -0,0 +1,86 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "smap.H" +#include "smapstatus.H" +#include <sstream> + +using namespace std; + +//////////////////////////////////////////////////////////////////////// +// +// STATUS + +const char *mail::smapSTATUS::getName() +{ + return "STATUS"; +} + +mail::smapSTATUS::smapSTATUS(string pathArg, + mail::callback::folderInfo &infoCallbackArg, + mail::callback &callbackArg) + : path(pathArg), infoCallback(infoCallbackArg) +{ + defaultCB= &callbackArg; +} + +mail::smapSTATUS::~smapSTATUS() +{ +} + +void mail::smapSTATUS::installed(imap &imapAccount) +{ + vector<string> words; + + path2words(path, words); + + vector<string>::iterator b=words.begin(), e=words.end(); + + string pstr=""; + + while (b != e) + { + pstr += " "; + pstr += imapAccount.quoteSMAP( *b ); + b++; + } + + + imapAccount.imapcmd("", (infoCallback.fastInfo + ? "STATUS CHEAP":"STATUS FULL") + + pstr + "\n"); +} + +bool mail::smapSTATUS::processLine(imap &imapAccount, + vector<const char *> &words) +{ + if (words.size() >= 2 && strcmp(words[0], "*") == 0 && + strcasecmp(words[1], "STATUS") == 0) + { + vector<const char *>::iterator b=words.begin() + 2; + + while (b != words.end()) + { + const char *c= *b++; + + if (strncasecmp(c, "EXISTS=", 7) == 0) + { + string s=c+7; + istringstream i(s); + + i >> infoCallback.messageCount; + } + else if (strncasecmp(c, "UNSEEN=", 7) == 0) + { + string s=c+7; + istringstream i(s); + + i >> infoCallback.unreadCount; + } + } + return true; + } + return smapHandler::processLine(imapAccount, words); +} diff --git a/libmail/smapstatus.H b/libmail/smapstatus.H new file mode 100644 index 0000000..909c57f --- /dev/null +++ b/libmail/smapstatus.H @@ -0,0 +1,38 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_smapstatus_H +#define libmail_smapstatus_H + +#include "libmail_config.h" +#include "mail.H" +#include "imap.H" +#include "smap.H" + +#include <vector> + +LIBMAIL_START + +class smapSTATUS : public smapHandler { + + std::string path; + mail::callback::folderInfo &infoCallback; + + const char *getName(); + +public: + smapSTATUS(std::string pathArg, + mail::callback::folderInfo &infoCallbackArg, + mail::callback &callbackArg); + ~smapSTATUS(); + void installed(imap &); + + bool processLine(imap &imapAccount, + std::vector<const char *> &words); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/smapstore.C b/libmail/smapstore.C new file mode 100644 index 0000000..614af12 --- /dev/null +++ b/libmail/smapstore.C @@ -0,0 +1,79 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ + +#include "smapstore.H" +#include <sstream> + +using namespace std; + +const char *mail::smapSTORE::getName() +{ + return "STORE"; +} + +mail::smapSTORE::smapSTORE(size_t messageNumArg, + string propsArg, + imap &imapAccount, + mail::callback &callback) + : uidSet(imapAccount, vector<size_t>(&messageNumArg, + &messageNumArg+1)), + props(propsArg) +{ + defaultCB= &callback; +} + +mail::smapSTORE::smapSTORE(const vector<size_t> &messages, + string propsArg, + imap &imapAccount, + mail::callback &callback) + : uidSet(imapAccount, messages), props(propsArg) +{ + defaultCB= &callback; +} + +mail::smapSTORE::~smapSTORE() +{ +} + +void mail::smapSTORE::installed(imap &imapAccount) +{ + msgRange.init(imapAccount, uidSet); + + uidSet.clear(); + + if (go()) + return; + + ok("OK"); + imapAccount.uninstallHandler(this); +} + +bool mail::smapSTORE::ok(std::string msg) +{ + if (go()) + { + doDestroy=false; + return true; + } + return smapHandler::ok(msg); +} + +bool mail::smapSTORE::go() +{ + std::ostringstream o; + + o << "STORE"; + + if (!myimap->currentFolder || + myimap->currentFolder->closeInProgress || + !(msgRange >> o)) + return false; + + o << " " << props << "\n"; + + myimap->imapcmd("", o.str()); + return true; +} diff --git a/libmail/smapstore.H b/libmail/smapstore.H new file mode 100644 index 0000000..6ba73ee --- /dev/null +++ b/libmail/smapstore.H @@ -0,0 +1,49 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_smapstore_H +#define libmail_smapstore_H + +#include "libmail_config.h" +#include "mail.H" +#include "imap.H" +#include "smap.H" + +#include "smapmsgrange.H" + +#include <vector> + +LIBMAIL_START + +class smapSTORE : public smapHandler { + + const char *getName(); + + smapUidSet uidSet; + smapMsgRange msgRange; + + std::string props; + +public: + smapSTORE(size_t messageNumArg, + std::string propsArg, + mail::imap &imapAccount, + mail::callback &call); + smapSTORE(const std::vector<size_t> &messages, + std::string propsArg, + mail::imap &imapAccount, + mail::callback &call); + ~smapSTORE(); + + void installed(imap &); + + bool ok(std::string); + + bool go(); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/smtp.C b/libmail/smtp.C new file mode 100644 index 0000000..e225f8d --- /dev/null +++ b/libmail/smtp.C @@ -0,0 +1,1420 @@ +/* +** Copyright 2002-2011, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "misc.H" +#include "smtp.H" +#include "driver.H" +#include "search.H" +#include "imaphmac.H" +#include "base64.H" +#include "smtpfolder.H" +#include "tcpd/spipe.h" +#include "unicode/unicode.h" +#include "libcouriertls.h" + +#include <errno.h> + +#include <sstream> +#include <iomanip> + +#include <sys/types.h> +#include <signal.h> +#if HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif +#ifndef WEXITSTATUS +#define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) +#endif +#ifndef WIFEXITED +#define WIFEXITED(stat_val) (((stat_val) & 255) == 0) +#endif + +using namespace std; + +#define SMTPTIMEOUT 60 + +#define INACTIVITY_TIMEOUT 30 + +#define DIGIT(c) (strchr("0123456789", (c))) + + +///////////////////////////////////////////////////////////////////////////// + +LIBMAIL_START + +static bool open_smtp(mail::account *&accountRet, + mail::account::openInfo &oi, + mail::callback &callback, + mail::callback::disconnect &disconnectCallback) +{ + mail::loginInfo nntpLoginInfo; + + if (!mail::loginUrlDecode(oi.url, nntpLoginInfo)) + return false; + + if (nntpLoginInfo.method != "smtp" && + nntpLoginInfo.method != "smtps" && + nntpLoginInfo.method != "sendmail") + return false; + + accountRet=new mail::smtp(oi.url, oi.pwd, oi.certificates, callback, + disconnectCallback, + oi.loginCallbackObj); + return true; +} + +static bool smtp_remote(string url, bool &flag) +{ + mail::loginInfo nntpLoginInfo; + + if (!mail::loginUrlDecode(url, nntpLoginInfo)) + return false; + + if (nntpLoginInfo.method != "smtp" && + nntpLoginInfo.method != "smtps") + return false; + + flag=true; + return true; +} + +driver smtp_driver= { &open_smtp, &smtp_remote }; + +LIBMAIL_END + +/////////////////////////////////////////////////////////////////////////// +// +// Helper class sends outbound message +// +/////////////////////////////////////////////////////////////////////////// + +mail::smtp::Blast::Blast(mail::smtp *smtpArg) + : eofSent(false), mySmtp(smtpArg), lastChar('\n'), + bytesTotal(0), bytesDone(0) +{ +} + +mail::smtp::Blast::~Blast() +{ + if (mySmtp) + mySmtp->myBlaster=NULL; +} + +bool mail::smtp::Blast::fillWriteBuffer() +{ + if (!mySmtp) + { + if (!eofSent) + { + eofSent=true; + writebuffer += "\r\n.\r\n"; + return true; + } + return false; + } + + mySmtp->installHandler(mySmtp->responseHandler); + // Reset the timeout every time the write buffer gets filled + + char buffer[BUFSIZ]; + + size_t i; + + FILE *fp=mySmtp->sendQueue.front().message; + + for (i=0; i<sizeof(buffer); i++) + { + int ch=getc(fp); + + if (ch == EOF) + { + writebuffer += string(buffer, buffer+i); + if (lastChar != '\n') + writebuffer += "\r\n"; + writebuffer += ".\r\n"; + + mail::callback *c=mySmtp->sendQueue.front().callback; + + if (c) + c->reportProgress(bytesTotal, bytesTotal, + 0, 1); + + mySmtp->myBlaster=NULL; + mySmtp=NULL; + eofSent=true; + return true; + } + + ++bytesDone; + + if (lastChar == '\n' && ch == '.') + { + ungetc(ch, fp); // Leading .s are doubled. + --bytesDone; + } + + if (ch == '\n' && lastChar != '\r') + { + ungetc(ch, fp); + --bytesDone; + ch='\r'; + } + + buffer[i]=lastChar=ch; + } + + mail::callback *c=mySmtp->sendQueue.front().callback; + + if (c) + c->reportProgress(bytesDone, bytesTotal, 0, 1); + + writebuffer += string(buffer, buffer+sizeof(buffer)); + return true; +} + +// Something arrived from the server. + +int mail::smtp::socketRead(const string &readbuffer) +{ + size_t n=readbuffer.find('\n'); + + if (n == std::string::npos) + return 0; + + string l=readbuffer.substr(0, n++); + + size_t cr; + + while ((cr=l.find('\r')) != std::string::npos) + l=l.substr(0, cr) + l.substr(cr+1); + + // Collect multiline SMTP replies. Keep only the last 30 lines + // received. + + smtpResponse.push_back(l); + + if (smtpResponse.size() > 30) + smtpResponse.erase(smtpResponse.begin()); + + string::iterator b=l.begin(), e=l.end(); + int numCode=0; + + while (b != e && isdigit((int)(unsigned char)*b)) + numCode=numCode * 10 + (*b++ - '0'); + + if (b == l.begin()+3) + { + if (b == e || unicode_isspace((unsigned char)*b)) + { + string s=""; + + list<string>::iterator b, e; + + b=smtpResponse.begin(); + e=smtpResponse.end(); + + while (b != e) + { + if (s.size() > 0) s += "\n"; + + s += *b++; + } + + smtpResponse.clear(); + + if (responseHandler != 0) + (this->*responseHandler)(numCode, s); + else + fatalError=true; + // SMTP msg when no handler installed, something's + // gone wrong. + + } + } + + return n; +} + +void mail::smtp::installHandler(void (mail::smtp::*handler)(int, + string)) +{ + responseHandler=handler; + responseTimeout=0; + + if (handler != 0) + { + time(&responseTimeout); + responseTimeout += SMTPTIMEOUT; + } +} + + +void mail::smtp::disconnect(const char *reason) +{ + while (!sendQueue.empty()) + { + struct messageQueueInfo &info=sendQueue.front(); + + info.callback->fail(reason); + fclose(info.message); + sendQueue.pop(); + } + pingTimeout=0; + + if (getfd() >= 0) + { + string errmsg=socketDisconnect(); + + if (errmsg.size() == 0) + errmsg=reason; + + if (orderlyShutdown) + success(errmsg); + else if (smtpLoginInfo.callbackPtr) + error(errmsg); + else + disconnect_callback.disconnected(errmsg.c_str()); + } +} + +void mail::smtp::resumed() +{ + if (responseHandler != 0) + { + time(&responseTimeout); + responseTimeout += SMTPTIMEOUT; + } +} + + +void mail::smtp::handler(vector<pollfd> &fds, int &ioTimeout) +{ + if (fatalError) + return; + + int fd_save=getfd(); + + time_t now; + + time(&now); + + if (responseHandler != 0) + { + if (now >= responseTimeout) + { + fatalError=true; + ioTimeout=0; + (this->*responseHandler)(500, "500 Server timed out."); + return; + } + + if (ioTimeout >= (responseTimeout - now) * 1000) + { + ioTimeout=(responseTimeout - now)*1000; + } + } + + if (pingTimeout) + { + if (pingTimeout < now) + { + mail::smtpInfo dummy; + + send(NULL, dummy, NULL, false); + } + else + { + if (ioTimeout >= (pingTimeout - now)*1000) + { + ioTimeout=(pingTimeout - now)*1000; + } + } + } + + + mail::fd::process(fds, ioTimeout); + + if (getfd() < 0) + { + size_t i; + + for (i=fds.size(); i; ) + { + --i; + + if (fds[i].fd == fd_save) + { + fds.erase(fds.begin()+i, fds.begin()+i+1); + break; + } + } + } +} + +mail::smtp::smtp(string url, string passwd, + std::vector<std::string> &certificates, + mail::callback &callback, + mail::callback::disconnect &disconnectCallback, + loginCallback *loginCallbackFunc) + : mail::fd(disconnectCallback, certificates), orderlyShutdown(false), + ready2send(false), fatalError(true), pingTimeout(0) +{ + responseHandler=NULL; + smtpLoginInfo.callbackPtr= &callback; + smtpLoginInfo.loginCallbackFunc=loginCallbackFunc; + + if (!loginUrlDecode(url, smtpLoginInfo)) + { + smtpLoginInfo.callbackPtr->fail("Invalid SMTP URL."); + return; + } + + if (smtpLoginInfo.method == "sendmail") // Local sendmail + { + smtpLoginInfo.use_ssl=false; + smtpLoginInfo.uid=""; + smtpLoginInfo.pwd=""; + + string errmsg; + + int pipefd[2]; + + if (libmail_streampipe(pipefd) < 0) + { + smtpLoginInfo.callbackPtr->fail(strerror(errno)); + return; + } + + pid_t pid1=fork(); + + if (pid1 < 0) + { + close(pipefd[0]); + close(pipefd[1]); + smtpLoginInfo.callbackPtr->fail(strerror(errno)); + return; + } + + if (pid1 == 0) + { + close(0); + close(1); + errno=EIO; + if (dup(pipefd[1]) != 0 || dup(pipefd[1]) != 1) + { + perror("500 dup"); + exit(1); + } + close(pipefd[0]); + close(pipefd[1]); + + pid1=fork(); + if (pid1 < 0) + { + printf("500 fork() failed.\r\n"); + exit(1); + } + + if (pid1) + exit(0); + execl(SENDMAIL, SENDMAIL, "-bs", (char *)NULL); + + printf("500 %s: %s\n", SENDMAIL, strerror(errno)); + exit(0); + } + + close(pipefd[1]); + int waitstat; + pid_t pid2; + + while ((pid2=wait(&waitstat)) != pid1) + { + if (kill(pid1, 0)) + break; + } + + + try { + errmsg=socketAttach(pipefd[0]); + } catch (...) { + close(pipefd[0]); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + } + else + { + smtpLoginInfo.use_ssl= smtpLoginInfo.method == "smtps"; + + if (passwd.size() > 0) + smtpLoginInfo.pwd=passwd; + + string errmsg=socketConnect(smtpLoginInfo, "smtp", "smtps"); + + if (errmsg.size() > 0) + { + smtpLoginInfo.callbackPtr->fail(errmsg); + return; + } + } + + fatalError=false; + hmac_method_index=0; + installHandler(&mail::smtp::greetingResponse); +} + +// Login failed + +void mail::smtp::error(string errMsg) +{ + mail::callback *c=smtpLoginInfo.callbackPtr; + + smtpLoginInfo.callbackPtr=NULL; + + if (c) + c->fail(errMsg); + +} + +// Login succeeded + +void mail::smtp::success(string errMsg) +{ + mail::callback *c=smtpLoginInfo.callbackPtr; + + smtpLoginInfo.callbackPtr=NULL; + + if (c) + c->success(errMsg); +} + +// Send EHLO/HELO + +void mail::smtp::howdy(const char *hi) +{ + char buffer[512]; + + buffer[sizeof(buffer)-1]=0; + + if (gethostname(buffer, sizeof(buffer)-1) < 0) + strcpy(buffer, "localhost"); + + socketWrite(string(hi) + buffer + "\r\n"); +} + +void mail::smtp::greetingResponse(int n, string s) +{ + switch (n / 100) { + case 1: + case 2: + case 3: + break; + default: + error(s); + return; + } + + installHandler(&mail::smtp::ehloResponse); + howdy("EHLO "); +} + +void mail::smtp::ehloResponse(int n, string s) +{ + switch (n / 100) { + case 1: + case 2: + case 3: + break; + default: // Maybe HELO will work + installHandler(&mail::smtp::heloResponse); + howdy("HELO "); + return; + } + + // Parse EHLO capability list + + bool firstLine=true; + + while (s.size() > 0) + { + string line; + + size_t p=s.find('\n'); + + if (p == std::string::npos) + { + line=s; + s=""; + } + else + { + line=s.substr(0, p); + s=s.substr(p+1); + } + + if (firstLine) + { + firstLine=false; + continue; + } + + while ((p=s.find('\r')) != std::string::npos) + s=s.substr(0, p) + s.substr(p+1); + + for (p=0; p<line.size(); p++) + if (!DIGIT((int)(unsigned char)line[p])) + break; + + if (p < line.size()) + p++; + + // Put capability to uppercase + + line=mail::iconvert::convert_tocase(line.substr(p), + "iso-8859-1", + unicode_uc); + + for (p=0; p<line.size(); p++) + if (unicode_isspace((unsigned char)line[p]) || + line[p] == '=') + break; + + string capa=line.substr(0, p); + + // Save SASL authentication methods as capabilities + // AUTH=method + + if (capa == "AUTH" || capa == "XSECURITY") + { + if (p < line.size()) + p++; + + line=line.substr(p); + + while (line.size() > 0) + { + for (p=0; p<line.size(); p++) + if (unicode_isspace((unsigned char) + line[p]) || + line[p] == ',') + break; + + if (p > 0) + { + capabilities.insert(capa + "=" + + line.substr(0, p)); + } + if (p < line.size()) + p++; + line=line.substr(p); + } + } + else + capabilities.insert(capa); + } + starttls(); +} + +void mail::smtp::heloResponse(int n, string s) +{ + switch (n / 100) { + case 1: + case 2: + case 3: + starttls(); + return; + } + + error(s); +} + +void mail::smtp::starttls() +{ +#if HAVE_LIBCOURIERTLS + + if (smtpLoginInfo.use_ssl // Already SSL-encrypted + || smtpLoginInfo.options.count("notls") > 0 + || !hasCapability("STARTTLS") + || socketEncrypted()) + { + begin_auth(); + return; + } + + installHandler(&mail::smtp::starttlsResponse); + + socketWrite("STARTTLS\r\n"); +#else + begin_auth(); + return; +#endif +} + +void mail::smtp::starttlsResponse(int n, string s) +{ + switch (n / 100) { + case 1: + case 2: + if (!socketBeginEncryption(smtpLoginInfo)) + { + smtpLoginInfo.callbackPtr=NULL; + return; + } + capabilities.clear(); + greetingResponse(n, s); + return; + } + + error(s); +} + +void mail::smtp::begin_auth() +{ + if (!hasCapability("AUTH=EXTERNAL")) + { + begin_auth_nonexternal(); + return; + } + installHandler(&mail::smtp::auth_external_response); + socketWrite("AUTH EXTERNAL =\r\n"); +} + +void mail::smtp::auth_external_response(int n, string s) +{ + switch (n / 100) { + case 1: + case 2: + case 3: + authenticated(); + return; + } + begin_auth_nonexternal(); +} + +void mail::smtp::begin_auth_nonexternal() +{ + int i; + + if (smtpLoginInfo.uid.size() == 0) + { + authenticate_hmac(); + return; + } + + for (i=0; mail::imaphmac::hmac_methods[i]; ++i) + { + if (hasCapability(string("AUTH=CRAM-") + + mail::imaphmac + ::hmac_methods[hmac_method_index]->getName() + )) + break; + } + + if (mail::imaphmac::hmac_methods[i] || + hasCapability("AUTH=LOGIN")) + { + if (smtpLoginInfo.pwd.size() == 0) + { + currentCallback=smtpLoginInfo.loginCallbackFunc; + + if (currentCallback) + { + currentCallback->target=this; + currentCallback->getPassword(); + return; + } + } + } + authenticate_hmac(); +} + +void mail::smtp::loginInfoCallback(std::string str) +{ + currentCallback=NULL; + smtpLoginInfo.pwd=str; + authenticate_hmac(); +} + +void mail::smtp::loginInfoCallbackCancel() +{ + currentCallback=NULL; + error("Login cancelled"); +} + +void mail::smtp::authenticate_hmac() +{ + for (;;) + { + if (mail::imaphmac::hmac_methods[hmac_method_index] == NULL || + smtpLoginInfo.uid.size() == 0) + { + authenticate_login(); // Exhausted SASL, forget it. + return; + } + + if (hasCapability(string("AUTH=CRAM-") + + mail::imaphmac + ::hmac_methods[hmac_method_index]->getName() + )) + break; + hmac_method_index++; + } + + installHandler(&mail::smtp::hmacResponse); + + socketWrite(string("AUTH CRAM-") + + mail::imaphmac::hmac_methods[hmac_method_index]->getName() + + "\r\n"); +} + +void mail::smtp::hmacResponse(int n, string s) +{ + switch (n / 100) { + case 1: + case 2: + authenticated(); + return; + } + + if ((n / 100) != 3 || s.size() < 4) + { + ++hmac_method_index; + authenticate_hmac(); + return; + } + + s=mail::decodebase64str(s.substr(4)); + s=(*mail::imaphmac::hmac_methods[hmac_method_index])(smtpLoginInfo + .pwd, s); + + string sHex; + + { + ostringstream hexEncode; + + hexEncode << hex; + + string::iterator b=s.begin(); + string::iterator e=s.end(); + + while (b != e) + hexEncode << setw(2) << setfill('0') + << (int)(unsigned char)*b++; + + sHex=hexEncode.str(); + } + + s=mail::encodebase64str(smtpLoginInfo.uid + " " + sHex); + socketWrite(s + "\r\n"); +} + +void mail::smtp::authenticate_login() +{ + if (!hasCapability("AUTH=LOGIN") || smtpLoginInfo.uid.size() == 0 || + smtpLoginInfo.options.count("cram")) + { + authenticated(); + return; + } + + installHandler(&mail::smtp::loginUseridResponse); + + socketWrite("AUTH LOGIN\r\n"); +} + +void mail::smtp::loginUseridResponse(int n, string msg) +{ + if ((n / 100) != 3) + { + error(msg); + return; + } + + installHandler(&mail::smtp::loginPasswdResponse); + socketWrite(string(mail::encodebase64str(smtpLoginInfo.uid)) + + "\r\n"); +} + +void mail::smtp::loginPasswdResponse(int n, string msg) +{ + if ((n / 100) != 3) + { + error(msg); + return; + } + + installHandler(&mail::smtp::loginResponse); + socketWrite(string(mail::encodebase64str(smtpLoginInfo.pwd)) + + "\r\n"); +} + +void mail::smtp::loginResponse(int n, string msg) +{ + if ((n / 100) != 2) + { + error(msg); + return; + } + + authenticated(); +} + +void mail::smtp::authenticated() +{ + ready2send=true; + if (!sendQueue.empty()) + startNextMessage(); // Already something's queued up. + else + pingTimeout=time(NULL) + INACTIVITY_TIMEOUT; + success("Connected to " + smtpLoginInfo.server); +} + +mail::smtp::~smtp() +{ + disconnect("Connection forcibly aborted."); +} + +void mail::smtp::logout(mail::callback &callback) +{ + if (fatalError) + { + callback.success("Server timed out."); + return; + } + + smtpLoginInfo.callbackPtr= &callback; + orderlyShutdown=true; + installHandler(&mail::smtp::quitResponse); + + socketWrite("QUIT\r\n"); +} + +void mail::smtp::quitResponse(int n, string s) +{ + if (!socketEndEncryption()) + { + socketDisconnect(); + success(s); + } +} + +void mail::smtp::checkNewMail(mail::callback &callback) // STUB +{ + callback.success("OK"); +} + +bool mail::smtp::hasCapability(string capability) +{ + if (capability == LIBMAIL_SINGLEFOLDER) + return false; + + return capabilities.count(capability) > 0; +} + +string mail::smtp::getCapability(string name) +{ + upper(name); + + if (name == LIBMAIL_SERVERTYPE) + { + return "smtp"; + } + + if (name == LIBMAIL_SERVERDESCR) + { + return "ESMTP server"; + } + + if (name == LIBMAIL_SINGLEFOLDER) + return ""; + + return capabilities.count(name) > 0 ? "1":""; +} + + +// Not implemented + +mail::folder *mail::smtp::folderFromString(string) +{ + errno=EINVAL; + return NULL; +} + +// Not implemented + +void mail::smtp::readTopLevelFolders(mail::callback::folderList &callback1, + mail::callback &callback2) +{ + vector<const mail::folder *> dummy; + + callback1.success(dummy); + callback2.success("OK"); +} + +// Not implemented + +void mail::smtp::findFolder(string folder, + class mail::callback::folderList &callback1, + class mail::callback &callback2) +{ + callback2.fail("Folder not found."); +} + +// This is why we're here: + +mail::folder *mail::smtp::getSendFolder(const mail::smtpInfo &info, + const mail::folder *folderArg, + string &errmsg) +{ + if (folderArg) + { + return mail::account::getSendFolder(info, folderArg, errmsg); + } + + if (info.recipients.size() == 0) + { + errno=ENOENT; + errmsg="Empty recipient list."; + return NULL; + } + + mail::smtpFolder *folder=new mail::smtpFolder(this, info); + + if (!folder) + { + errmsg=strerror(errno); + return NULL; + } + return folder; +} + + +void mail::smtp::readMessageAttributes(const vector<size_t> &messages, + MessageAttributes attributes, + mail::callback::message &callback) +{ + callback.fail("Invalid message number."); +} + + +void mail::smtp::readMessageContent(const vector<size_t> &messages, + bool peek, + enum mail::readMode readType, + mail::callback::message &callback) +{ + callback.fail("Invalid message number."); +} + +void mail::smtp::readMessageContent(size_t messageNum, + bool peek, + const mail::mimestruct &msginfo, + enum mail::readMode readType, + mail::callback::message &callback) +{ + callback.fail("Invalid message number."); +} + +void mail::smtp::readMessageContentDecoded(size_t messageNum, + bool peek, + const mail::mimestruct &msginfo, + mail::callback::message &callback) +{ + callback.fail("Invalid message number."); +} + +size_t mail::smtp::getFolderIndexSize() +{ + return 0; +} + +mail::messageInfo mail::smtp::getFolderIndexInfo(size_t messageNum) +{ + return mail::messageInfo(); +} + +void mail::smtp::saveFolderIndexInfo(size_t messageNumber, + const mail::messageInfo &info, + mail::callback &callback) +{ + callback.fail("Invalid message number."); +} + +void mail::smtp::updateFolderIndexFlags(const vector<size_t> &messages, + bool doFlip, + bool enableDisable, + const mail::messageInfo &flags, + mail::callback &callback) +{ + callback.fail("Invalid message number."); +} + +void mail::smtp::updateFolderIndexInfo(mail::callback &callback) +{ + callback.success("OK"); +} + +void mail::smtp::removeMessages(const std::vector<size_t> &messages, + mail::callback &cb) +{ + updateFolderIndexInfo(cb); +} + +void mail::smtp::copyMessagesTo(const vector<size_t> &messages, + mail::folder *copyTo, + mail::callback &callback) +{ + callback.fail("Invalid message number."); +} + +void mail::smtp::searchMessages(const mail::searchParams &searchInfo, + mail::searchCallback &callback) +{ + vector<size_t> empty; + + callback.success(empty); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Another message is ready to go out. Called by mail::smtpFolder::go, +// and one other place. + +void mail::smtp::send(FILE *tmpfile, mail::smtpInfo &info, + mail::callback *callback, bool flag8bit) +{ + pingTimeout=0; + + struct messageQueueInfo msgInfo; + + msgInfo.message=tmpfile; + msgInfo.messageInfo=info; + msgInfo.callback=callback; + msgInfo.flag8bit=flag8bit; + + bool wasEmpty=sendQueue.empty(); + + sendQueue.push(msgInfo); + + if (wasEmpty && ready2send) + startNextMessage(); +} + +void mail::smtp::startNextMessage() +{ + if (fatalError) + { + messageProcessed(500, "500 A fatal error occurred while connected to remote server."); + return; + } + + socketWrite("RSET\r\n"); + installHandler(&mail::smtp::rsetResponse); +} + +void mail::smtp::rsetResponse(int result, string message) +{ + struct messageQueueInfo &info=sendQueue.front(); + + if (info.message == NULL) // Just a NO-OP ping. + { + sendQueue.pop(); + + if (!sendQueue.empty()) + startNextMessage(); + else + pingTimeout=time(NULL) + INACTIVITY_TIMEOUT; + return; + } + + switch (result / 100) { + case 1: + case 2: + case 3: + break; + default: + messageProcessed(result, message); + return; + } + + if (!validString(info.messageInfo.sender)) + return; + + { + vector<string>::iterator b=info.messageInfo.recipients.begin(), + e=info.messageInfo.recipients.end(); + + while (b != e) + if (!validString(*b++)) + return; + } + + { + map<string, string>::iterator + b=info.messageInfo.options.begin(), + e=info.messageInfo.options.end(); + + while (b != e) + if (!validString((*b++).second)) + return; + } + + string mailfrom="MAIL FROM:<" + info.messageInfo.sender + ">"; + + if (hasCapability("8BITMIME")) + mailfrom += info.flag8bit ? " BODY=8BITMIME":" BODY=7BIT"; + + if (hasCapability("SIZE")) + { + long pos; + + if (fseek(info.message, 0L, SEEK_END) < 0 || + (pos=ftell(info.message)) < 0) + { + messageProcessed(500, strerror(errno)); + return; + } + + pos= pos < 1024 * 1024 ? pos * 70 / 68: pos/68 * 70; + + string buffer; + + { + ostringstream o; + + o << pos; + buffer=o.str(); + } + + mailfrom += " SIZE="; + mailfrom += buffer; + } + + if (info.messageInfo.options.count("DSN") > 0 || + info.messageInfo.options.count("RET") > 0) + { + if (!hasCapability("DSN")) + { + messageProcessed(500, "500 This mail server does not support delivery status notifications."); + return; + } + } + + if (hasCapability("DSN")) + { + if (info.messageInfo.options.count("RET") > 0) + mailfrom += " RET=" + info.messageInfo.options + .find("RET")->second; + } + + if (hasCapability("XVERP=COURIER")) + { + if (info.messageInfo.options.count("VERP") > 0) + mailfrom += " VERP"; + } + + if (info.messageInfo.options.count("SECURITY") > 0) + { + string sec=info.messageInfo.options.find("SECURITY")->second; + + if (info.messageInfo.options.count("XSECURITY=" + sec) == 0) + { + messageProcessed(500, "500 requested security not available."); + return; + } + + mailfrom += " SECURITY=" + sec; + } + + pipelineCmd=mailfrom; + + mailfrom += "\r\n"; + + pipelineCount=0; + installHandler(&mail::smtp::mailfromResponse); + socketWrite(mailfrom); +} + +// RCPT TO handler in pipeline mode. + +void mail::smtp::pipelinedResponse(int result, string message) +{ + switch (result / 100) { + case 1: + case 2: + case 3: + break; + default: + if (pipelineError.size() == 0) + pipelineError=sendQueue.front().messageInfo + .recipients[rcptCount] + ": " + message; + break; + } + + ++rcptCount; + if (--pipelineCount || fatalError) + return; // More on the way + + if (pipelineError.size() > 0) + messageProcessed(500, pipelineError); + else + { + installHandler(&mail::smtp::dataResponse); + socketWrite("DATA\r\n"); + } +} + +// MAIL FROM response. RCPT TO response in non-pipelined mode. + +void mail::smtp::mailfromResponse(int result, string message) +{ + switch (result / 100) { + case 1: + case 2: + case 3: + break; + default: + messageProcessed(result, pipelineCmd + ": " + message); + return; + } + + struct messageQueueInfo &info=sendQueue.front(); + + rcptCount=0; + if (pipelineCount == 0 && hasCapability("PIPELINING") && + smtpLoginInfo.options.count("nopipelining") == 0) + { + string rcptto=""; + + vector<string>::iterator b=info.messageInfo.recipients.begin(), + e=info.messageInfo.recipients.end(); + + pipelineError=""; + + while (b != e) + { + rcptto += "RCPT TO:<" + *b++ + ">"; + + if (info.messageInfo.options.count("DSN") > 0) + rcptto += " NOTIFY=" + + info.messageInfo.options.find("DSN") + ->second; + rcptto += "\r\n"; + ++pipelineCount; + } + + installHandler(&mail::smtp::pipelinedResponse); + socketWrite(rcptto); + return; + } + + if (pipelineCount < info.messageInfo.recipients.size()) + { + pipelineCmd=info.messageInfo.recipients[pipelineCount++]; + string rcptto= "RCPT TO:<" + pipelineCmd + ">"; + + if (info.messageInfo.options.count("DSN") > 0) + rcptto += " NOTIFY=" + + info.messageInfo.options.find("DSN") + ->second; + + rcptto += "\r\n"; + socketWrite(rcptto); + return; + } + + installHandler(&mail::smtp::dataResponse); + socketWrite("DATA\r\n"); +} + +// 1st DATA response + +void mail::smtp::dataResponse(int result, string message) +{ + switch (result / 100) { + case 1: + case 2: + case 3: + break; + default: + messageProcessed(result, message); + break; + } + + long fpos; + + if ( fseek(sendQueue.front().message, 0L, SEEK_END) < 0 || + (fpos=ftell(sendQueue.front().message)) < 0 || + fseek(sendQueue.front().message, 0L, SEEK_SET) < 0 || + (myBlaster=new Blast(this)) == NULL) + + { + fatalError=true; + messageProcessed(500, strerror(errno)); + return; + } + + myBlaster->bytesTotal=fpos; + installHandler(&mail::smtp::data2Response); + socketWrite(myBlaster); +} + +void mail::smtp::data2Response(int result, string message) +{ + if (myBlaster) + myBlaster->mySmtp=NULL; + installHandler(NULL); // Nothing more. + messageProcessed(result, message); +} + +bool mail::smtp::validString(string s) +{ + string::iterator b=s.begin(), e=s.end(); + + while (b != e) + { + if ((int)(unsigned char)*b <= ' ' || *b == '>' || *b == '<') + { + messageProcessed(500, "500 Invalid character in message sender, recipient, or options."); + return false; + } + b++; + } + return true; +} + +void mail::smtp::messageProcessed(int result, string message) +{ + struct messageQueueInfo &info=sendQueue.front(); + + mail::callback *callback=info.callback; + fclose(info.message); + + try { + sendQueue.pop(); + if (!sendQueue.empty()) + startNextMessage(); + else + pingTimeout=time(NULL) + INACTIVITY_TIMEOUT; + + switch (result / 100) { + case 1: + case 2: + case 3: + callback->success(message); + break; + default: + callback->fail(message); + break; + } + + } catch (...) { + callback->fail("An exception has occurred while processing message."); + } +} + +string mail::smtp::translatePath(string path) +{ + return "INBOX"; // NOOP +} diff --git a/libmail/smtp.H b/libmail/smtp.H new file mode 100644 index 0000000..80adfe6 --- /dev/null +++ b/libmail/smtp.H @@ -0,0 +1,239 @@ +/* +** Copyright 2002-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_smtp_H +#define libmail_smtp_H + +#include "libmail_config.h" +#if HAVE_FCNTL_H +#include <fcntl.h> +#endif +#include "mail.H" +#include <sys/types.h> + +#include "smtpinfo.H" +#include "logininfo.H" +#include "fd.H" +#include <stdio.h> +#include <time.h> +#include <list> +#include <string> +#include <set> +#include <queue> +#include <vector> + +LIBMAIL_START + +/////////////////////////////////////////////////////////////////////////// +// +// An SMTP implementation +// + +class smtp : public fd, public loginInfo::callbackTarget { + +private: + void resumed(); + void handler(std::vector<pollfd> &fds, int &timeout); + + std::set<std::string> capabilities; + + bool orderlyShutdown; + + loginInfo smtpLoginInfo; + + int socketRead(const std::string &readbuffer); + + void disconnect(const char *reason); + + std::list<std::string> smtpResponse; + // SMTP server response being accumulated. + + void (smtp::*responseHandler)(int numCode, + std::string multilineResponse); + // Handler called when an SMTP response is received in entirety + + void installHandler(void (smtp::*)(int numCode, + std::string multilineResponse)); + // Install next handler. + + time_t responseTimeout; + // SMTP timeout + + void error(std::string); /* Login failed */ + void success(std::string); /* Login succeeded */ + + void howdy(const char *); /* send ehlo/helo */ + + void greetingResponse(int, std::string); + void ehloResponse(int, std::string); + void heloResponse(int, std::string); + void auth_external_response(int, std::string); + void quitResponse(int, std::string); + + int hmac_method_index; + /* Next HMAC authentication hash function to try */ + + void authenticate_hmac(); + void hmacResponse(int, std::string); + + void starttls(); + void starttlsResponse(int, std::string); + + void authenticate_login(); + + void begin_auth(); + void begin_auth_nonexternal(); + + void loginInfoCallback(std::string str); + void loginInfoCallbackCancel(); + + + void loginUseridResponse(int, std::string); + void loginPasswdResponse(int, std::string); + void loginResponse(int, std::string); + + void authenticated(); + + bool ready2send; /* Fully logged in */ + + struct messageQueueInfo { + FILE *message; + mail::smtpInfo messageInfo; + mail::callback *callback; + bool flag8bit; + }; + + std::queue<messageQueueInfo> sendQueue; + /* Queue of messages waiting to go out */ + + bool fatalError; // Fatal connection error occured + time_t pingTimeout; // When idling, ping occasionally + + // Custom WriteBuffer that spits out the whole message + + class Blast : public fd::WriteBuffer { + + bool eofSent; + public: + smtp *mySmtp; + + char lastChar; + + size_t bytesTotal; + size_t bytesDone; + + Blast(smtp *smtpArg); + ~Blast(); + + bool fillWriteBuffer(); + }; + + Blast *myBlaster; + +public: + friend class Blast; + + smtp(std::string url, std::string passwd, + std::vector<std::string> &certificates, + mail::callback &callback, + mail::callback::disconnect &disconnectCallback, + loginCallback *loginCallbackFunc); + + smtp(const smtp &); // UNDEFINED + smtp &operator=(const smtp &); // UNDEFINED + + ~smtp(); + + void logout(mail::callback &callback); + void checkNewMail(mail::callback &callback); + bool hasCapability(std::string capability); + std::string getCapability(std::string capability); + + mail::folder *folderFromString(std::string); + + void readTopLevelFolders(mail::callback::folderList &callback1, + mail::callback &callback2); + + void findFolder(std::string folder, + class mail::callback::folderList &callback1, + class mail::callback &callback2); + std::string translatePath(std::string path); + + mail::folder *getSendFolder(const mail::smtpInfo &info, + const mail::folder *folder, + std::string &errmsg); + + + void readMessageAttributes(const std::vector<size_t> &messages, + MessageAttributes attributes, + mail::callback::message &callback); + + void readMessageContent(const std::vector<size_t> &messages, + bool peek, + enum mail::readMode readType, + mail::callback::message &callback); + + void readMessageContent(size_t messageNum, + bool peek, + const mimestruct &msginfo, + enum mail::readMode readType, + mail::callback::message &callback); + + void readMessageContentDecoded(size_t messageNum, + bool peek, + const mimestruct &msginfo, + mail::callback::message &callback); + + size_t getFolderIndexSize(); + mail::messageInfo getFolderIndexInfo(size_t); + + void saveFolderIndexInfo(size_t, + const mail::messageInfo &, + mail::callback &); + + void updateFolderIndexFlags(const std::vector<size_t> &messages, + bool doFlip, + bool enableDisable, + const mail::messageInfo &flags, + mail::callback &callback); + + void updateFolderIndexInfo(mail::callback &); + void removeMessages(const std::vector<size_t> &messages, + callback &cb); + + void copyMessagesTo(const std::vector<size_t> &messages, + mail::folder *copyTo, + mail::callback &callback); + + void searchMessages(const searchParams &searchInfo, + searchCallback &callback); + + + void send(FILE *tmpfile, mail::smtpInfo &info, + mail::callback *callback, + bool flag8bit); + +private: + void startNextMessage(); + void messageProcessed(int, std::string); + + void rsetResponse(int, std::string); + bool validString(std::string s); + + std::string pipelineError; + unsigned pipelineCount; // Count pipelined replies received + unsigned rcptCount; // RCPT TO count. + std::string pipelineCmd; // Command that got rejected. + + void pipelinedResponse(int, std::string); + void mailfromResponse(int, std::string); + + void dataResponse(int, std::string); + void data2Response(int, std::string); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/smtpfolder.C b/libmail/smtpfolder.C new file mode 100644 index 0000000..23f3e5b --- /dev/null +++ b/libmail/smtpfolder.C @@ -0,0 +1,282 @@ +/* +** Copyright 2002-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "smtpfolder.H" +#include <errno.h> +#include "rfc2045/rfc2045.h" +#include <cstring> + +using namespace std; + +mail::smtpFolder::smtpFolder(mail::smtp *mySmtp, + const mail::smtpInfo &myInfo) + : mail::folder(mySmtp), smtpServer(mySmtp), + smtpSendInfo(myInfo) +{ +} + +mail::smtpFolder::~smtpFolder() +{ +} + +void mail::smtpFolder::sameServerAsHelperFunc() const +{ +} + +string mail::smtpFolder::getName() const +{ + return "SMTP"; +} + +string mail::smtpFolder::getPath() const +{ + return "SMTP"; +} + +bool mail::smtpFolder::isParentOf(string path) const +{ + return false; +} + +bool mail::smtpFolder::hasMessages() const +{ + return true; +} + +bool mail::smtpFolder::hasSubFolders() const +{ + return false; +} + +void mail::smtpFolder::hasMessages(bool dummy) +{ +} + +void mail::smtpFolder::hasSubFolders(bool dummy) +{ +} + +void mail::smtpFolder::getParentFolder(callback::folderList &callback1, + callback &callback2) const +{ + callback2.fail("Not implemented"); +} + +void mail::smtpFolder::readFolderInfo( mail::callback::folderInfo &callback1, + mail::callback &callback2) const +{ + callback1.success(); + callback2.success("OK"); +} + +void mail::smtpFolder::readSubFolders( mail::callback::folderList &callback1, + mail::callback &callback2) const +{ + vector<const mail::folder *> dummy; + + callback1.success(dummy); + callback2.success("OK"); +} + +mail::addMessage *mail::smtpFolder::addMessage(mail::callback &callback) const +{ + if (smtpServer.isDestroyed()) + { + callback.fail("Server connection closed."); + return NULL; + } + + mail::smtpAddMessage *msgp=new mail::smtpAddMessage(smtpServer, + smtpSendInfo, + callback); + if (!msgp) + { + callback.fail(strerror(errno)); + return NULL; + } + + return msgp; +} + +void mail::smtpFolder::createSubFolder(string name, bool isDirectory, + mail::callback::folderList &callback1, + mail::callback &callback2) const +{ + callback2.fail("Cannot create subfolder."); +} + +void mail::smtpFolder::create(bool isDirectory, mail::callback &callback) const +{ + callback.success("OK"); +} + +void mail::smtpFolder::destroy(mail::callback &callback, bool destroyDir) const +{ + callback.success("OK"); +} + +void mail::smtpFolder::renameFolder(const mail::folder *newParent, + std::string newName, + mail::callback::folderList &callback1, + callback &callback) const +{ + callback.fail("Not implemented."); +} + +mail::folder *mail::smtpFolder::clone() const +{ + mail::smtpFolder *c=new mail::smtpFolder(smtpServer, smtpSendInfo); + + if (!c) + return NULL; + + c->smtpSendInfo=smtpSendInfo; + + return c; +} + +string mail::smtpFolder::toString() const +{ + return "SMTP"; +} + +void mail::smtpFolder::open(mail::callback &openCallback, + mail::snapshot *restoreSnapshot, + mail::callback::folder &folderCallback) const +{ + openCallback.success("OK"); +} + +////////////////////////////////////////////////////////////////////////// +// +// mail::smtpAddMessage collects the message into a temporary file. +// If the smtpAccount server does not support 8bit mail, 8bit messages are rewritten +// as quoted printable. mail::smtp::send() receives the end result. + +mail::smtpAddMessage::smtpAddMessage(mail::smtp *smtpServer, + mail::smtpInfo messageInfo, + mail::callback &callback) + : mail::addMessage(smtpServer), + myServer(smtpServer), + myInfo(messageInfo), + myCallback(callback), + temporaryFile(NULL), + rfcp(NULL), + flag8bit(false) +{ + if (!smtpServer->hasCapability("8BITMIME")) + { + if ((rfcp=rfc2045_alloc_ac()) == NULL) + LIBMAIL_THROW("Out of memory."); + + // Might need rewriting + + } + + if ((temporaryFile=tmpfile()) == NULL) + { + if (rfcp) + rfc2045_free(rfcp); + rfcp=NULL; + LIBMAIL_THROW("Out of memory."); + } +} + +mail::smtpAddMessage::~smtpAddMessage() +{ + if (temporaryFile) + fclose(temporaryFile); + if (rfcp) + rfc2045_free(rfcp); +} + +void mail::smtpAddMessage::saveMessageContents(string s) +{ + string::iterator b=s.begin(), e=s.end(); + + while (b != e) + if (*b++ & 0x80) + flag8bit=true; + if (fwrite(&*s.begin(), s.size(), 1, temporaryFile) != 1) + ; // Ignore gcc warning. + if (rfcp) + rfc2045_parse(rfcp, &*s.begin(), s.size()); +} + +void mail::smtpAddMessage::fail(string errmsg) +{ + myCallback.fail(errmsg); + delete this; +} + +void mail::smtpAddMessage::go() +{ + try { + if (fflush(temporaryFile) || ferror(temporaryFile)) + { + fail(strerror(errno)); + return; + } + + if (myServer.isDestroyed()) + return; + + if (rfcp) + { + rfc2045_parse_partial(rfcp); + if (rfc2045_ac_check(rfcp, RFC2045_RW_7BIT)) + { + FILE *tmpfile2=tmpfile(); + if (!tmpfile2) + { + fail(strerror(errno)); + return; + } + + int fd_copy=dup(fileno(tmpfile2)); + + if (fd_copy < 0) + { + fclose(tmpfile2); + fail(strerror(errno)); + return; + } + + struct rfc2045src *src= + rfc2045src_init_fd + (fileno(temporaryFile)); + + if (src == NULL || + rfc2045_rewrite(rfcp, + src, + fd_copy, + "mail::account") < 0) + { + if (src) + rfc2045src_deinit(src); + close(fd_copy); + fclose(tmpfile2); + fail(strerror(errno)); + return; + } + close(fd_copy); + fclose(temporaryFile); + temporaryFile=tmpfile2; + flag8bit=false; + rfc2045src_deinit(src); + } + } + myServer->send(temporaryFile, myInfo, &myCallback, + flag8bit); + temporaryFile=NULL; + delete this; + } catch (...) { + fail("An exception occurred while sending message."); + delete this; + } +} + + diff --git a/libmail/smtpfolder.H b/libmail/smtpfolder.H new file mode 100644 index 0000000..348d800 --- /dev/null +++ b/libmail/smtpfolder.H @@ -0,0 +1,109 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_smtpfolder_H +#define libmail_smtpfolder_H + +#include "libmail_config.h" +#include "mail.H" +#include "smtp.H" +#include "smtpinfo.H" +#include "addmessage.H" + +#include <stdio.h> + +// smtp::getSendFolder() creates a fake folder object. Copying +// messages to the fake folder sends them via SMTP. + +struct rfc2045; + +LIBMAIL_START + +class smtpFolder : public mail::folder { + + ptr<smtp> smtpServer; + + smtpFolder(const smtpFolder &); //UNDEFINED + smtpFolder &operator=(const smtpFolder &); //UNDEFINED + + smtpInfo smtpSendInfo; + +public: + smtpFolder(smtp *, const smtpInfo &smtpInfo); + ~smtpFolder(); + + void sameServerAsHelperFunc() const; + std::string getName() const; + std::string getPath() const; + bool hasMessages() const; + bool hasSubFolders() const; + + bool isParentOf(std::string) const; + + void hasMessages(bool); + void hasSubFolders(bool); + + void getParentFolder(callback::folderList &callback1, + callback &callback2) const; + + void readFolderInfo( mail::callback::folderInfo &callback1, + mail::callback &callback2) const; + + void readSubFolders( mail::callback::folderList &callback1, + mail::callback &callback2) const; + + mail::addMessage *addMessage(mail::callback &callback) const; + + void create(bool isDirectory, mail::callback &callback) const; + + void createSubFolder(std::string name, bool isDirectory, + mail::callback::folderList &callback1, + mail::callback &callback2) const; + + void destroy(mail::callback &callback, bool destroyDir) const; + + void renameFolder(const mail::folder *newParent, + std::string newName, + mail::callback::folderList &callback1, + callback &callback) const; + + mail::folder *clone() const; + + std::string toString() const; + + void open(mail::callback &openCallback, + snapshot *restoreSnapshot, + mail::callback::folder &folderCallback) const; +}; + +///////////////////////////////////////////////////////////////////////// +// +// addMessage object created by smtpFolder::addMessage() + +class smtpAddMessage : public addMessage { + + ptr<smtp> myServer; + smtpInfo myInfo; + mail::callback &myCallback; + + FILE *temporaryFile; + struct rfc2045 *rfcp; + bool flag8bit; + +public: + smtpAddMessage(smtp *smtpServer, + smtpInfo messageInfo, + mail::callback &callback); + + ~smtpAddMessage(); + + void saveMessageContents(std::string); + void go(); + void fail(std::string errmsg); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/smtpinfo.H b/libmail/smtpinfo.H new file mode 100644 index 0000000..c5dde86 --- /dev/null +++ b/libmail/smtpinfo.H @@ -0,0 +1,43 @@ +/* +** Copyright 2002, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_smtpinfo_h +#define libmail_smtpinfo_h + +#include <string> +#include <vector> +#include <map> + +#include "namespace.H" + +// +// This structure holds options for sending mail. +// + +LIBMAIL_START + +class smtpInfo { +public: + std::string sender; + // SMTP envelope sender + + std::vector<std::string> recipients; + // SMTP envelope recipients + + std::map<std::string, std::string> options; + // Options. Currently defined options: + // notls + // cram + // DSN=never,success,fail,delay + // RET=hdrs,full + // NOPIPELINING + // VERP (Courier extension) + // SECURITY (extension) + // POST (recipient list is empty, we think this is an nntp server) +}; + +LIBMAIL_END + +#endif diff --git a/libmail/snapshot.C b/libmail/snapshot.C new file mode 100644 index 0000000..d3c1ed3 --- /dev/null +++ b/libmail/snapshot.C @@ -0,0 +1,25 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "snapshot.H" + +using namespace std; + +mail::snapshot::restore::restore() +{ +} + +mail::snapshot::restore::~restore() +{ +} + +mail::snapshot::snapshot() +{ +} + +mail::snapshot::~snapshot() +{ +} + diff --git a/libmail/snapshot.H b/libmail/snapshot.H new file mode 100644 index 0000000..fb1236d --- /dev/null +++ b/libmail/snapshot.H @@ -0,0 +1,69 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_snapshot_H +#define libmail_snapshot_H + +#include "libmail_config.h" +#include "namespace.H" + +#include <string> +#include <set> + +LIBMAIL_START + +class messageInfo; + +// +// Some drivers can open a folder faster if folder snapshots are used. +// At predetermined times, those drives invoke callback::folder::saveSnapshot() +// with a unique snapshot identifier. The application shoud use +// mail::account::getFolderIndexSize(), then getFolderIndexInfo() and save +// the folder index's contents that way. +// +// When the application reopens th folder, the application can pass a pointer +// to the snapshot object to mail::folder::open(). +// +// getSnapshotInfo() should return the snapshot ID, and the number of messages +// in the folder. Eventually, restoreSnapshot() will be invoked. +// restoreSnapshot() receives a reference to a restore object, +// restoreSnapshot() should repeatedly invoke restore->restoreIndex() to +// populate the index. restoreIndex() takes a message index number, and a +// messageInfo object. The index can be restored in any order, but all index +// entries must be restored. +// Additionally, if any messages had any keywords set, restoreKeywords() +// must be invoked, in any order, to restore the list of keywords set for +// the given message. +// +// Alternatively, abortRestore() may be invoked if the application cannot +// restore the index fully, for some reason. + +class snapshot { + +public: + class restore { + public: + restore(); + virtual ~restore(); + + virtual void restoreIndex(size_t msgNum, + const mail::messageInfo &)=0; + virtual void restoreKeywords(size_t msgNum, + const std::set<std::string> &)=0; + virtual void abortRestore()=0; + }; + + snapshot(); + virtual ~snapshot(); + + virtual void getSnapshotInfo(std::string &snapshotId, + size_t &nMessages)=0; + + virtual void restoreSnapshot(restore &)=0; +}; + +LIBMAIL_END + +#endif diff --git a/libmail/sortfolders.C b/libmail/sortfolders.C new file mode 100644 index 0000000..dbc2637 --- /dev/null +++ b/libmail/sortfolders.C @@ -0,0 +1,35 @@ +/* +** Copyright 2002-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "mail.H" +#include <cstring> + +mail::folder::sort::sort(bool foldersFirstArg) // TRUE: sort folders first + : foldersFirst(foldersFirstArg) +{ +} + +mail::folder::sort::~sort() +{ +} + +bool mail::folder::sort::operator()(const mail::folder *a, + const mail::folder *b) +{ + int an=a->hasSubFolders() ? 1:0; + int bn=b->hasSubFolders() ? 1:0; + + if (!foldersFirst) + { + an= 1-an; + bn= 1-bn; + } + + if (an != bn) + return an < bn; + + return (strcoll(a->getName().c_str(), b->getName().c_str()) < 0); +} diff --git a/libmail/structure.C b/libmail/structure.C new file mode 100644 index 0000000..5d91df8 --- /dev/null +++ b/libmail/structure.C @@ -0,0 +1,350 @@ +/* +** Copyright 2002-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "misc.H" +#include "mail.H" +#include "structure.H" +#include "envelope.H" +#include "rfcaddr.H" +#include <cstring> + +#include "rfc822/rfc2047.h" +#include "rfc2045/rfc2045.h" +#include "unicode/unicode.h" + +#include <errno.h> + +#include <map> +#include <string> + +using namespace std; + +mail::mimestruct::parameterList::parameterList() +{ +} + +mail::mimestruct::parameterList::~parameterList() +{ +} + +static int ins_param(const char *param, + const char *value, + void *voidp) +{ + string v=value; + + if (v.size() > 2 && v[0] == '"' && v[v.size()-1] == '"') + v=v.substr(1, v.size()-2); + + ((std::map<string, string> *)voidp) + ->insert(make_pair(string(param), v)); + return 0; +} + +void mail::mimestruct::parameterList::set(string name, string value, + string charset, + string language) +{ + iterator p, b, e; + + mail::upper(name); + p=begin(); + e=end(); + + size_t s=name.size(); + + while (p != e) + { + b=p++; + if (b->first == name) + { + param_map.erase(b); + continue; + } + + if (b->first.size() > s && + b->first.substr(0, s) == name && + b->first[s] == '*') // RFC 2231 + { + param_map.erase(b); + continue; + } + } + + rfc2231_attrCreate(name.c_str(), + value.c_str(), + charset.c_str(), + language.c_str(), + ins_param, + ¶m_map); +} + +bool mail::mimestruct::parameterList::exists(string name) const +{ + mail::upper(name); + + const_iterator b, e; + + b=begin(); + e=end(); + + size_t s=name.size(); + + while (b != e) + { + if (b->first == name) + return true; + + if (b->first.size() > s && + b->first.substr(0, s) == name && + b->first[s] == '*') // RFC 2231 + return true; + b++; + } + return false; +} + +string mail::mimestruct::parameterList::get(string name, + string chset) + const +{ + mail::upper(name); + + struct rfc2231param *paramList=NULL; + + const_iterator b, e; + + b=begin(); + e=end(); + + string stringRet; + + try { + while (b != e) + { + if (rfc2231_buildAttrList(¶mList, + name.c_str(), + b->first.c_str(), + b->second.c_str()) < 0) + LIBMAIL_THROW("Out of memory."); + + b++; + } + + int charsetLen; + int langLen; + int textLen; + + rfc2231_paramDecode(paramList, NULL, NULL, NULL, + &charsetLen, + &langLen, + &textLen); + + vector<char> charsetStr, langStr, textStr; + + charsetStr.insert(charsetStr.begin(), charsetLen, 0); + langStr.insert(langStr.begin(), langLen, 0); + textStr.insert(textStr.begin(), textLen, 0); + + rfc2231_paramDecode(paramList, &*charsetStr.begin(), + &*langStr.begin(), + &*textStr.begin(), + &charsetLen, + &langLen, + &textLen); + + stringRet=&textStr[0]; + + if (chset.size() > 0 && charsetStr[0]) + { + bool err; + + string s=mail::iconvert::convert(stringRet, + &charsetStr[0], + chset, + err); + + if (!err) + stringRet=s; + } + + rfc2231_paramDestroy(paramList); + + } catch (...) { + rfc2231_paramDestroy(paramList); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + return stringRet; +} + +std::string mail::mimestruct::parameterList::toString(string s) const +{ + const_iterator b=begin(), e=end(); + + while (b != e) + { + s += ";\n "; + + s += b->first; + if (b->second.size()) + { + s += "="; + + string::const_iterator p=b->second.begin(), + q=b->second.end(); + + while (p != q) + { + if (!rfc2047_qp_allow_word(*p)) + break; + ++p; + } + + if (p != q) + { + s += "\""; + + for (p=b->second.begin(); p != q; ) + { + string::const_iterator r=p; + + while (p != q) + { + if (*p == '"' || *p == '\\') + break; + ++p; + } + + s += string(r, p); + + if (p != q) + { + s += "\\"; + s += string(p, p+1); + ++p; + } + } + s += "\""; + } + else + s += b->second; + } + ++b; + } + + return s; +} + +mail::mimestruct::mimestruct() + : content_size(0), content_lines(0), + message_rfc822_envelope(0), parent(0) +{ +} + +mail::mimestruct::~mimestruct() +{ + destroy(); +} + +mail::mimestruct::mimestruct(const mail::mimestruct &cpy) + : content_size(0), content_lines(0), + message_rfc822_envelope(0), parent(0) +{ + (*this)=cpy; +} + +mail::mimestruct &mail::mimestruct::operator=(const mail::mimestruct &cpy) +{ + destroy(); + + vector<mail::mimestruct *>::const_iterator + b=cpy.children.begin(), + e=cpy.children.end(); + + while (b != e) + { + (*addChild())= **b; + b++; + } + +#define CPY(x) (x)=(cpy.x) + + CPY(mime_id); + CPY(type); + CPY(subtype); + CPY(type_parameters); + CPY(content_id); + CPY(content_description); + CPY(content_transfer_encoding); + CPY(content_size); + CPY(content_lines); + CPY(content_md5); + CPY(content_language); + CPY(content_disposition); + CPY(content_disposition_parameters); +#undef CPY + + if (cpy.message_rfc822_envelope) + { + message_rfc822_envelope=new mail::envelope; + + if (!message_rfc822_envelope) + LIBMAIL_THROW("Out of memory."); + + *message_rfc822_envelope= *cpy.message_rfc822_envelope; + } + + return *this; +} + +mail::envelope &mail::mimestruct::getEnvelope() +{ + if (!message_rfc822_envelope) + if ((message_rfc822_envelope=new mail::envelope) == NULL) + LIBMAIL_THROW("Out of memory."); + return *message_rfc822_envelope; +} + +void mail::mimestruct::destroy() +{ + mail::envelope *p=message_rfc822_envelope; + + if (p) + { + message_rfc822_envelope=NULL; + delete p; + } + + vector<mail::mimestruct *>::iterator b=children.begin(), + e=children.end(); + + while (b != e) + { + delete *b; + b++; + } + children.clear(); +} + +mail::mimestruct *mail::mimestruct::addChild() +{ + mail::mimestruct *p=new mail::mimestruct; + + if (!p) + LIBMAIL_THROW("Out of memory."); + + try { + p->parent=this; + children.push_back(p); + } catch (...) + { + delete p; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + return p; +} diff --git a/libmail/structure.H b/libmail/structure.H new file mode 100644 index 0000000..8798019 --- /dev/null +++ b/libmail/structure.H @@ -0,0 +1,185 @@ +/* +** Copyright 2002-2005, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_structure_H +#define libmail_structure_H + +#include "libmail_config.h" + +#include <string> +#include <vector> +#include <map> + +#include "namespace.H" +#include "misc.H" + +LIBMAIL_START + +class envelope; +class xenvelope; + +/////////////////////////////////////////////////////////////////////////// +// +// The structure of a message is described by a tree of mail::mimestruct +// objects. Each mail::mimestruct objects represents a MIME section in the +// message, and may contain pointers to mail::mimestruct objects for content +// sections if the MIME section is a multipart section. + +class mimestruct { + +public: + + // The Content-Type: and Content-Disposition: headers have an + // optional parameter=value; list, which is parsed into a hash. + // + // The get() method fetches the parameter. It's a pretty smart get() + // method. If a character set is provided, the parameter value is + // mapped to the specified character set. Additionally, the get() + // method automatically handles RFC 2231-encoded attributes. + // Ain't that cool? + + class parameterList { + std::map<std::string, std::string> param_map; + + public: + typedef std::map<std::string, std::string>::iterator iterator; + typedef std::map<std::string, std::string>::const_iterator + const_iterator; + + iterator begin() { return param_map.begin(); } + iterator end() { return param_map.end(); } + const_iterator begin() const { return param_map.begin(); } + const_iterator end() const { return param_map.end(); } + + parameterList(); + ~parameterList(); + + void set_simple(std::string name, std::string value) + { + mail::upper(name); + param_map.insert(std::make_pair(name, value)); + } + + void erase(std::string name) + { + mail::upper(name); + iterator p=param_map.find(name); + + if (p != end()) + param_map.erase(p); + } + + void set(std::string name, std::string value, + std::string charset, + std::string language=""); + + bool exists(std::string name) const; + std::string get(std::string name, + std::string chset="") const; + + std::string toString(std::string type) const; + // Create a header from "type", plus these parameters + }; + + std::string mime_id; // An opaque identifier of this MIME section. + + + std::string type, subtype; // text, plain + + bool messagerfc822() const + { + return type == "MESSAGE" && subtype == "RFC822"; + } + + bool multipartsigned() const + { + return type == "MULTIPART" && subtype == "SIGNED"; + } + + bool multipartencrypted() const + { + return type == "MULTIPART" && subtype == "ENCRYPTED"; + } + + parameterList type_parameters; + + // <charset, iso-8859-1> + + std::string content_id; + std::string content_description; + std::string content_transfer_encoding; + + size_t content_size; + size_t content_lines; + + std::string content_md5; + std::string content_language; + + std::string content_disposition; + + parameterList content_disposition_parameters; + + mimestruct(); + ~mimestruct(); + + // Copy constructors, and assignment operators, are fully functional. + + mimestruct(const mimestruct &cpy); + mimestruct &operator=(const mimestruct &cpy); + +private: + mail::envelope *message_rfc822_envelope; + + std::vector<mimestruct *> children; + mimestruct *parent; + + void destroy(); +public: + + // MESSAGE/RFC822 content also has an associated mail::envelope + // object. getEnvelope() automatically creates this object, + // which is automatically destroyed by the destructor + + class mail::envelope &getEnvelope(); + const class mail::envelope &getEnvelope() const + { + return *message_rfc822_envelope; + } + + mimestruct *addChild(); + size_t getNumChildren() const { return children.size(); } + + const mimestruct *getChild(size_t n) + const { return children[n]; } + + const mimestruct *getParent() const { return parent; } + + mimestruct *getChild(size_t n) { return children[n]; } + mimestruct *getParent() { return parent; } + + mimestruct *find(std::string mime_idArg) + { + if (mime_idArg == mime_id) + return this; + + std::vector<mimestruct *>::iterator b=children.begin(), + e=children.end(); + + while (b != e) + { + mimestruct *p= (*b)->find(mime_idArg); + + ++b; + + if (p) + return p; + } + + return NULL; + } +}; +LIBMAIL_END + +#endif diff --git a/libmail/sync.C b/libmail/sync.C new file mode 100644 index 0000000..8544526 --- /dev/null +++ b/libmail/sync.C @@ -0,0 +1,1176 @@ +/* +** Copyright 2002-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "mail.H" +#include "addmessage.H" +#include "imap.H" +#include "imaplogin.H" +#include "sync.H" +#include "envelope.H" +#include "structure.H" +#include "rfcaddr.H" +#include "search.H" +#include "smtpinfo.H" +#include <cstring> + +#include <iostream> +#include <map> +#include <errno.h> +#include <stdio.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +using namespace std; + +class mail::ACCOUNT::disconnectCallback : public mail::callback::disconnect { + + mail::ACCOUNT &account; +public: + disconnectCallback(mail::ACCOUNT &p); + ~disconnectCallback(); + + void disconnected(const char *errmsg); + void servererror(const char *errmsg); +}; + +mail::ACCOUNT::disconnectCallback::disconnectCallback(mail::ACCOUNT &p) + : account(p) +{ +} + +mail::ACCOUNT::disconnectCallback::~disconnectCallback() +{ +} + +void mail::ACCOUNT::disconnectCallback::disconnected(const char *errmsg) +{ + if (errmsg == NULL || *errmsg==0) + errmsg="Connection closed by remote host."; + + account.disconnected(errmsg); +} + +void mail::ACCOUNT::disconnectCallback::servererror(const char *errmsg) +{ + if (account.errmsg.size() == 0) + account.errmsg=errmsg; +} + +/////////////////////////////////////////////////////////////////////////// +// +// Internally-maintained folder list. +// + +mail::ACCOUNT::FolderList::FolderList() +{ +} + +mail::ACCOUNT::FolderList::~FolderList() +{ + vector<mail::folder *>::iterator b=folders.begin(), e=folders.end(); + + while (b != e) + { + delete *b; + b++; + } +} + +mail::ACCOUNT::FolderList::FolderList(const FolderList &l) +{ + (*this)=l; +} + +mail::ACCOUNT::FolderList &mail::ACCOUNT::FolderList::operator=(const mail::ACCOUNT::FolderList &l) +{ + vector<mail::folder *>::const_iterator b=l.folders.begin(), + e=l.folders.end(); + + while (b != e) + append(*b++); + return *this; +} + +void mail::ACCOUNT::FolderList::append(const mail::folder *f) +{ + mail::folder *g=f->clone(); // Create my own copy + + if (g == NULL) + LIBMAIL_THROW("Out of memory."); + + try { + folders.push_back(g); + } catch (...) + { + delete g; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +mail::ACCOUNT::Store::Store() +{ +} + +mail::ACCOUNT::Store::~Store() +{ +} + +class mail::ACCOUNT::callback : public mail::callback { + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); +public: + bool failed; + bool succeeded; + + mail::ACCOUNT &me; + + callback(mail::ACCOUNT &mePtr); + ~callback(); + + void success(string message); + void fail(string message); +}; + +mail::ACCOUNT::callback::callback(mail::ACCOUNT &mePtr) : failed(false), + succeeded(false), + me(mePtr) +{ + me.errmsg=""; // Convenient place to clear the errmsg +} + +mail::ACCOUNT::callback::~callback() +{ +} + +void mail::ACCOUNT::callback::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + if (me.currentProgressReporter) + me.currentProgressReporter->operator() + (bytesCompleted, bytesEstimatedTotal, + messagesCompleted, + messagesEstimatedTotal); +} + +void mail::ACCOUNT::callback::success(string message) +{ + me.errmsg=message; + succeeded=true; +} + +void mail::ACCOUNT::callback::fail(string message) +{ + me.errmsg=message; + failed=true; +} + +////////////////////////////////////////////////////////////////////////////// + +mail::ACCOUNT::Progress::Progress(mail::ACCOUNT *acct) + : reportingProgress(false), myAccount(acct) +{ + if (acct) + { + if (acct->currentProgressReporter) + { + acct->currentProgressReporter->reportingProgress + =false; + // Get rid of previous progress + // reporter + } + acct->currentProgressReporter=this; + reportingProgress=true; + } +} + +mail::ACCOUNT::Progress::~Progress() +{ + if (reportingProgress) + { + myAccount->currentProgressReporter=NULL; + } +} + +////////////////////////////////////////////////////////////////////////////// + +mail::ACCOUNT::ACCOUNT() : ptr(NULL), currentProgressReporter(NULL), + imap_disconnect(NULL) +{ + folderCallback.account=this; +} + +mail::ACCOUNT::~ACCOUNT() +{ + if (currentProgressReporter) + { + currentProgressReporter->reportingProgress=false; + currentProgressReporter=NULL; + } + + logout(); +} + +mail::ACCOUNT::FolderCallback::FolderCallback() : account(NULL) +{ +} + +mail::ACCOUNT::FolderCallback::~FolderCallback() +{ +} + +void mail::ACCOUNT::FolderCallback::newMessages() +{ + account->folderModifiedFlag=true; +} + +void mail::ACCOUNT::FolderCallback::messagesRemoved(vector< pair<size_t, + size_t> > &dummy) +{ + account->folderModifiedFlag=true; +} + +void mail::ACCOUNT::FolderCallback::messageChanged(size_t dummy) +{ + account->folderModifiedFlag=true; +} + +//////////////////////////////////////////////////////////////////////// +// +// All method calls eventually wind up here. They create a callback +// object, call mail::account, then wait for the callback object to fire back, +// here. + +bool mail::ACCOUNT::eventloop(mail::ACCOUNT::callback &callback) +{ + for (;;) + { + vector<pollfd> fds; + int ioTimeout; + + mail::account::process(fds, ioTimeout); + + if (callback.succeeded || callback.failed) + break; + + if (mail::account::poll(fds, ioTimeout) < 0) + { + if (errno != EINTR) + return false; + } + } + + return callback.succeeded; +} + +//////////////////////////////////////////////////////////////////////// +// +// Invoked by mail::ACCOUNT::disconnectCallback when mail::account reports a disconnection +// + +void mail::ACCOUNT::disconnected(string e) +{ + if (errmsg.size() == 0) + errmsg=e; + + if (ptr != NULL) + { + mail::account *s=ptr; + + + ptr=NULL; + delete s; + } +} + +bool mail::ACCOUNT::disconnected() +{ + if (ptr == NULL) + { + if (errmsg.size() == 0) + errmsg="Lost server connection."; + return true; + } + return false; +} + +bool mail::ACCOUNT::login(mail::account::openInfo openInfoArg) +{ + if (ptr) + { + mail::account *s=ptr; + ptr=NULL; + delete s; + } + + if (imap_disconnect) + delete imap_disconnect; + + ptr=NULL; + imap_disconnect=NULL; + + imap_disconnect=new mail::ACCOUNT::disconnectCallback(*this); + + mail::ACCOUNT::callback callback( *this ); + + if (!imap_disconnect || + !(ptr=mail::account::open(openInfoArg, + callback, *imap_disconnect))) + { + return false; + } + + return (eventloop(callback)); +} + +void mail::ACCOUNT::logout() +{ + errmsg=""; + + if (ptr) // Still logged on. + { + mail::ACCOUNT::callback callback( *this ); + ptr->logout(callback); + eventloop(callback); + } + + if (ptr) + { + mail::account *p=ptr; + ptr=NULL; + delete p; + } + + if (imap_disconnect) + delete imap_disconnect; + imap_disconnect=NULL; +} + +/////////////////////////////////////////////////////////////////////////// +// +// Handle mail::account calls that return a folder list. Copy the list to +// our object. +// + +class mail::ACCOUNT::readFoldersCallback : public mail::callback::folderList { +public: + mail::ACCOUNT::FolderList &list; + + readFoldersCallback(mail::ACCOUNT::FolderList &myList); + ~readFoldersCallback(); + + void success(const vector<const mail::folder *> &folders); +}; + + + +mail::ACCOUNT::readFoldersCallback +::readFoldersCallback(mail::ACCOUNT::FolderList &myList) : list(myList) +{ +} + +mail::ACCOUNT::readFoldersCallback +::~readFoldersCallback() +{ +} + +void mail::ACCOUNT::readFoldersCallback +::success(const vector<const mail::folder *> &folders) +{ + vector<const mail::folder *>::const_iterator + b=folders.begin(), e=folders.end(); + + while (b != e) + { + list.append( *b++ ); + } +} + +bool mail::ACCOUNT::getTopLevelFolders(mail::ACCOUNT::FolderList &list) +{ + if (disconnected()) + return false; + + mail::ACCOUNT::readFoldersCallback callback1(list); + mail::ACCOUNT::callback callback2 (*this); + + ptr->readTopLevelFolders(callback1, callback2); + + return eventloop(callback2); +} + +bool mail::ACCOUNT::getSubFolders(const mail::folder *folder, + mail::ACCOUNT::FolderList &list) +{ + if (disconnected()) + return false; + + mail::ACCOUNT::readFoldersCallback callback1(list); + mail::ACCOUNT::callback callback2 (*this); + + folder->readSubFolders(callback1, callback2); + + return eventloop(callback2); +} + +bool mail::ACCOUNT::getParentFolder(const mail::folder *folder, + mail::ACCOUNT::FolderList &list) +{ + if (disconnected()) + return false; + + mail::ACCOUNT::readFoldersCallback callback1(list); + mail::ACCOUNT::callback callback2 (*this); + + folder->getParentFolder(callback1, callback2); + + return eventloop(callback2); +} + +mail::folder *mail::ACCOUNT::getFolderFromString(string name) +{ + if (disconnected()) + return NULL; + return ptr->folderFromString(name); +} + +mail::folder *mail::ACCOUNT::getFolderFromPath(string path) +{ + if (disconnected()) + return NULL; + + mail::ACCOUNT::FolderList list; + mail::ACCOUNT::readFoldersCallback callback1(list); + mail::ACCOUNT::callback callback2 (*this); + + ptr->findFolder(path, callback1, callback2); + + if (!eventloop(callback2)) + return NULL; + + if (list.size() != 1) + { + errmsg="Internal protocol error."; + return NULL; + } + + mail::folder *f=list[0]->clone(); + + if (!f) + errmsg=strerror(errno); + return f; +} + +std::string mail::ACCOUNT::translatePath(std::string path) +{ + if (disconnected()) + return ""; + + errno=0; + + path=ptr->translatePath(path); + + if (path.size() == 0) + errmsg=errno ? strerror(errno):""; + return path; +} + +bool mail::ACCOUNT::folderModified() +{ + bool f=folderModifiedFlag; + + folderModifiedFlag=false; + return f; +} + +mail::folder *mail::ACCOUNT::createFolder(const mail::folder *folder, + string name, bool isdir) +{ + if (disconnected()) + return NULL; + + mail::ACCOUNT::FolderList list; + mail::ACCOUNT::readFoldersCallback callback1(list); + mail::ACCOUNT::callback callback2 (*this); + + folder->createSubFolder(name, isdir, callback1, callback2); + + if (!eventloop(callback2) || list.size() == 0) + return NULL; + + return list[0]->clone(); +} + +mail::folder *mail::ACCOUNT::renameFolder(const mail::folder *folder, + const mail::folder *newParent, + string name) +{ + if (disconnected()) + return NULL; + + mail::ACCOUNT::FolderList list; + mail::ACCOUNT::readFoldersCallback callback1(list); + mail::ACCOUNT::callback callback2 (*this); + + folder->renameFolder(newParent, name, callback1, callback2); + + if (!eventloop(callback2) || list.size() == 0) + return NULL; + + return list[0]->clone(); +} + +bool mail::ACCOUNT::createFolder(const mail::folder *folder, bool isdir) +{ + if (disconnected()) + return false; + + mail::ACCOUNT::callback callback2 (*this); + + folder->create(isdir, callback2); + + return eventloop(callback2); +} + +bool mail::ACCOUNT::deleteFolder(const mail::folder *folder, bool deleteDir) +{ + if (disconnected()) + return false; + + mail::ACCOUNT::callback callback(*this); + + folder->destroy(callback, deleteDir); + + return eventloop(callback); +} + +bool mail::ACCOUNT::checkNewMail() +{ + if (disconnected()) + return false; + + mail::ACCOUNT::callback callback(*this); + ptr->checkNewMail(callback); + eventloop(callback); + return folderModified(); +} + +bool mail::ACCOUNT::saveFolderIndexInfo(size_t messageNum, + const mail::messageInfo &newInfo) +{ + if (disconnected()) + return false; + + mail::ACCOUNT::callback callback(*this); + + ptr->saveFolderIndexInfo(messageNum, newInfo, callback); + return eventloop(callback); +} + +bool mail::ACCOUNT::updateFolderIndexInfo() +{ + if (disconnected()) + return false; + + mail::ACCOUNT::callback callback(*this); + + ptr->updateFolderIndexInfo(callback); + + return eventloop(callback); +} + +bool mail::ACCOUNT::removeMessages(const vector<size_t> &messages) +{ + if (disconnected()) + return false; + + mail::ACCOUNT::callback callback(*this); + + ptr->removeMessages(messages, callback); + + return eventloop(callback); +} + +bool mail::ACCOUNT::updateFolderIndexFlags(const vector<size_t> &messages, + bool doFlip, + bool enableDisable, + const mail::messageInfo &flags) +{ + if (disconnected()) + return false; + + mail::ACCOUNT::callback callback(*this); + + ptr->updateFolderIndexFlags(messages, doFlip, enableDisable, flags, + callback); + + return eventloop(callback); +} + +bool mail::ACCOUNT::copyMessagesTo(const vector<size_t> &messages, + mail::folder *copyTo) +{ + if (disconnected()) + return false; + + mail::ACCOUNT::callback callback(*this); + + ptr->copyMessagesTo(messages, copyTo, callback); + return eventloop(callback); +} + +bool mail::ACCOUNT::moveMessagesTo(const vector<size_t> &messages, + mail::folder *copyTo) +{ + if (disconnected()) + return false; + + mail::ACCOUNT::callback callback(*this); + + ptr->moveMessagesTo(messages, copyTo, callback); + return eventloop(callback); +} + +mail::ACCOUNT::FolderInfo::FolderInfo() + : messageCount(0), unreadCount(0), fastInfo(false) +{ +} + +mail::ACCOUNT::FolderInfo::~FolderInfo() +{ +} + +bool mail::ACCOUNT::readFolderInfo(const mail::folder *folder, FolderInfo &info) +{ + if (disconnected()) + return false; + + mail::ACCOUNT::callback callback(*this); + mail::callback::folderInfo callback2; + + callback2.fastInfo=info.fastInfo; + + folder->readFolderInfo(callback2, callback); + + bool rc=eventloop(callback); + + info.unreadCount=callback2.unreadCount; + info.messageCount=callback2.messageCount; + return rc; +} + +bool mail::ACCOUNT::openFolder(const mail::folder *folder, + mail::snapshot *restoreSnapshot) +{ + if (disconnected()) + return false; + + mail::ACCOUNT::callback callback(*this); + + folder->open(callback, restoreSnapshot, folderCallback); + bool f=eventloop(callback); + + if (f) + folderModifiedFlag=false; // Clean slate, now. + return f; +} + +size_t mail::ACCOUNT::getFolderIndexSize() +{ + if (disconnected()) + return 0; + + return ptr->getFolderIndexSize(); +} + +mail::messageInfo mail::ACCOUNT::getFolderIndexInfo(size_t n) +{ + if (n >= getFolderIndexSize()) + return mail::messageInfo(); + + return ptr->getFolderIndexInfo(n); +} + +void mail::ACCOUNT::getFolderKeywordInfo(size_t msgNum, + set<string> &keywords) +{ + if (msgNum >= getFolderIndexSize()) + { + keywords.clear(); + return; + } + return ptr->getFolderKeywordInfo(msgNum, keywords); +} + +bool mail::ACCOUNT::updateKeywords(const std::vector<size_t> &messages, + const std::set<std::string> &keywords, + bool setOrChange, + // false: set, true: see changeTo + bool changeTo) +{ + if (disconnected()) + return false; + + mail::ACCOUNT::callback callback(*this); + + ptr->updateKeywords(messages, keywords, setOrChange, changeTo, + callback); + return eventloop(callback); +} + +bool mail::ACCOUNT::updateKeywords(const std::vector<size_t> &messages, + const std::vector<std::string> &keywords, + bool setOrChange, + // false: set, true: see changeTo + bool changeTo) +{ + set<string> keySet; + + keySet.insert(keywords.begin(), keywords.end()); + return updateKeywords(messages, keySet, setOrChange, changeTo); +} + +bool mail::ACCOUNT::updateKeywords(const std::vector<size_t> &messages, + const std::list<std::string> &keywords, + bool setOrChange, + // false: set, true: see changeTo + bool changeTo) +{ + set<string> keySet; + + keySet.insert(keywords.begin(), keywords.end()); + return updateKeywords(messages, keySet, setOrChange, changeTo); +} + +//////////////////////////////////////////////////////////////////////////// +// +// Combo callback object handles whatever mail::callback::message throws +// at it, and sorts it based on the original message number set. +// + +class mail::ACCOUNT::readMessageCallback : public mail::callback::message , + public mail::ACCOUNT::callback { + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); +public: + map<size_t, mail::envelope> envelopeList; + map<size_t, vector<string> > referenceList; + + map<size_t, mail::mimestruct> structureList; + map<size_t, time_t> arrivalList; + map<size_t, unsigned long> sizeList; + + mail::ACCOUNT::Store *store; + + readMessageCallback(mail::ACCOUNT &me, + mail::ACCOUNT::Store *storeArg=NULL); + ~readMessageCallback(); + + void success(string message); + void fail(string message); + + void messageEnvelopeCallback(size_t messageNumber, + const mail::envelope &envelope); + void messageReferencesCallback(size_t messageNumber, + const vector<std::string> &references); + + void messageArrivalDateCallback(size_t messageNumber, + time_t datetime); + + void messageSizeCallback(size_t messageNumber, + unsigned long size); + + void messageStructureCallback(size_t messageNumber, + const mail::mimestruct + &messageStructure); + + void messageTextCallback(size_t n, string text); +}; + + +mail::ACCOUNT::readMessageCallback +::readMessageCallback(mail::ACCOUNT &me, + mail::ACCOUNT::Store *storeArg) + : mail::ACCOUNT::callback(me), store(storeArg) +{ +} + +mail::ACCOUNT::readMessageCallback::~readMessageCallback() +{ +} + +void mail::ACCOUNT +::readMessageCallback::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ +} + +void mail::ACCOUNT::readMessageCallback::success(string message) +{ + mail::ACCOUNT::callback::success(message); +} + +void mail::ACCOUNT::readMessageCallback::fail(string message) +{ + mail::ACCOUNT::callback::fail(message); +} + +void mail::ACCOUNT::readMessageCallback +::messageEnvelopeCallback(size_t messageNumber, + const mail::envelope &envelope) +{ + envelopeList.insert(make_pair(messageNumber, envelope)); +} + + +void mail::ACCOUNT::readMessageCallback +::messageReferencesCallback(size_t messageNumber, + const vector<std::string> &references) +{ + referenceList.insert(make_pair(messageNumber, references)); +} + +void mail::ACCOUNT::readMessageCallback +::messageArrivalDateCallback(size_t messageNumber, + time_t datetime) +{ + arrivalList.insert(make_pair(messageNumber, datetime)); +} + +void mail::ACCOUNT::readMessageCallback +::messageSizeCallback(size_t messageNumber, + unsigned long size) +{ + sizeList.insert(make_pair(messageNumber, size)); +} + +void mail::ACCOUNT::readMessageCallback +::messageStructureCallback(size_t messageNumber, + const mail::mimestruct &messageStructure) +{ + structureList.insert(make_pair(messageNumber, messageStructure)); +} + +void mail::ACCOUNT::readMessageCallback::messageTextCallback(size_t n, + string text) +{ + if (store) + store->store(n, text); +} + +/////////////////////////////////////////////////////////////////////////// + +bool mail::ACCOUNT::getMessageEnvelope(const vector<size_t> &messages, + vector<mail::xenvelope> &envelopes) +{ + envelopes.clear(); + + if (disconnected()) + return false; + + mail::ACCOUNT::readMessageCallback callback(*this); + + ptr->readMessageAttributes(messages, + mail::account::ARRIVALDATE | + mail::account::ENVELOPE | + mail::account::MESSAGESIZE, + callback); + + if (!eventloop(callback)) + return false; + + vector<size_t>::const_iterator b=messages.begin(), e=messages.end(); + + while (b != e) + { + size_t n= *b++; + + mail::xenvelope x; + + if (callback.envelopeList.count(n) > 0) + x=callback.envelopeList.find(n)->second; + if (callback.referenceList.count(n) > 0) + x.references=callback.referenceList.find(n)->second; + if (callback.arrivalList.count(n) > 0) + x.arrivalDate=callback.arrivalList.find(n)->second; + if (callback.sizeList.count(n) > 0) + x.messageSize=callback.sizeList.find(n)->second; + envelopes.push_back(x); + } + + return true; +} + +bool mail::ACCOUNT::getMessageStructure(const vector<size_t> &messages, + vector<mail::mimestruct> &structures) +{ + structures.clear(); + + if (disconnected()) + return false; + + mail::ACCOUNT::readMessageCallback callback(*this); + + ptr->readMessageAttributes(messages, mail::account::MIMESTRUCTURE, callback); + + if (!eventloop(callback)) + return false; + + vector<size_t>::const_iterator b=messages.begin(), e=messages.end(); + + while (b != e) + { + size_t n= *b++; + + if (callback.structureList.count(n) == 0) + structures.push_back(mail::mimestruct()); + else + structures.push_back(callback.structureList.find(n) + ->second); + } + + return true; +} + +mail::xenvelope mail::ACCOUNT::getMessageEnvelope(size_t messageNum) +{ + vector<size_t> vec; + vector<mail::xenvelope> envs; + + vec.push_back(messageNum); + + if (getMessageEnvelope(vec, envs)) + return envs[0]; + return mail::xenvelope(); +} + +mail::mimestruct mail::ACCOUNT::getMessageStructure(size_t messageNum) +{ + vector<size_t> vec; + vector<mail::mimestruct> strs; + + vec.push_back(messageNum); + + if (getMessageStructure(vec, strs)) + return strs[0]; + return mail::mimestruct(); +} + +bool mail::ACCOUNT::getMessageContent(const vector<size_t> &messages, + bool peek, + enum mail::readMode getType, + mail::ACCOUNT::Store &contentCallback) +{ + if (disconnected()) + return false; + + mail::ACCOUNT::readMessageCallback callback(*this, &contentCallback); + + ptr->readMessageContent(messages, peek, getType, callback); + + return (eventloop(callback)); +} + +bool mail::ACCOUNT::addMessage(const mail::folder *folder, + mail::addMessagePull &message) +{ + if (disconnected()) + return false; + + mail::ACCOUNT::callback callback(*this); + + mail::addMessage *ptr=folder->addMessage(callback); + + if (!ptr) + return false; + + try { + ptr->messageInfo=message.messageInfo; + ptr->messageDate=message.messageDate; + + string s; + + while ((s=message.getMessageContents()).size() > 0) + ptr->saveMessageContents(s); + ptr->go(); + } catch (...) { + ptr->fail("APPEND interrupted."); + } + + return eventloop(callback); +} + +// +// Collect mail::searchCallback results. +// + +class mail::ACCOUNT::SearchCallback : public mail::searchCallback { + + mail::ACCOUNT::callback &callback; + + vector<size_t> &found; + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + +public: + SearchCallback(mail::ACCOUNT::callback &callbackArg, + vector<size_t> &foundArg); + + ~SearchCallback(); + + void success(const vector<size_t> &found); + void fail(string); +}; + +mail::ACCOUNT::SearchCallback +::SearchCallback(mail::ACCOUNT::callback &callbackArg, + vector<size_t> &foundArg) + : callback(callbackArg), found(foundArg) +{ +} + +mail::ACCOUNT::SearchCallback::~SearchCallback() +{ +} + +void mail::ACCOUNT +::SearchCallback::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ +} + +void mail::ACCOUNT::SearchCallback::success(const vector<size_t> &foundArg) +{ + found.insert(found.end(), foundArg.begin(), foundArg.end()); + callback.success("SEARCH completed."); +} + +void mail::ACCOUNT::SearchCallback::fail(string message) +{ + callback.fail(message); +} + + +bool mail::ACCOUNT::searchMessages(const mail::searchParams &searchInfo, + vector<size_t> &messageList) +{ + if (disconnected()) + return false; + + mail::ACCOUNT::callback callback(*this); + + mail::ACCOUNT::SearchCallback search_callback(callback, messageList); + + ptr->searchMessages(searchInfo, search_callback); + + return eventloop(callback); +} + +bool mail::ACCOUNT::send(const mail::smtpInfo &messageInfo, + const mail::folder *saveFolder, + mail::addMessagePull &message) +{ + if (disconnected()) + return false; + + bool rc; + + mail::folder *sendFolder= + ptr->getSendFolder(messageInfo, saveFolder, errmsg); + + if (!sendFolder) + return false; + + try { + rc=addMessage(sendFolder, message); + delete sendFolder; + } catch (...) { + delete sendFolder; + errmsg="An exception occurred while sending the message"; + rc=false; + } + return rc; +} + +bool mail::ACCOUNT::getMyRights(const mail::folder *folder, string &rights) +{ + if (disconnected()) + return false; + + mail::ACCOUNT::callback callback (*this); + + folder->getMyRights(callback, rights); + + return eventloop(callback); +} + +bool mail::ACCOUNT::getRights(const mail::folder *folder, + list<pair<string, string> > &rights) +{ + if (disconnected()) + return false; + + mail::ACCOUNT::callback callback (*this); + + folder->getRights(callback, rights); + + return eventloop(callback); +} + +bool mail::ACCOUNT::setRights(const mail::folder *folder, + string &errorIdentifier, + vector<string> &errorRights, + string identifier, + string rights) +{ + if (disconnected()) + return false; + + mail::ACCOUNT::callback callback (*this); + + folder->setRights(callback, errorIdentifier, + errorRights, + identifier, + rights); + + return eventloop(callback); +} + +bool mail::ACCOUNT::delRights(const mail::folder *folder, + string &errorIdentifier, + vector<string> &errorRights, + string identifier) +{ + if (disconnected()) + return false; + + mail::ACCOUNT::callback callback (*this); + + folder->delRights(callback, errorIdentifier, + errorRights, + identifier); + + return eventloop(callback); +} diff --git a/libmail/sync.H b/libmail/sync.H new file mode 100644 index 0000000..439ff4a --- /dev/null +++ b/libmail/sync.H @@ -0,0 +1,401 @@ +/* +** Copyright 2002-2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_sync_H +#define libmail_sync_H + +#include "libmail_config.h" +#include <string> +#include <vector> +#include <list> +#include <set> +#include <iostream> + +//////////////////////////////////////////////////////////////////////// +// +// A synchronous libmail.a interface. +// +// USAGE: +// +// bool ok=mail->login(string url, string password); +// +// mail->logout(); +// +// mail::ACCOUNT::FolderList folderList; +// +// bool ok=mail->getTopLevelFolders(folderList); +// +// size_t folderCnt=folderList.size(); +// +// const mail::folder *folder=folderList[n]; +// +// bool ok=mail->getSubFolders(folder, folderList); +// +// bool ok=mail->getParentFolder(folder, folderList); +// +// string folderStr=folder->toString(); +// +// string folderPath=folder->getPath(); +// +// mail::folder *f=mail->getFolderFromString(folderStr); +// +// mail::folder *p=mail->getFolderFromPath(folderPath); +// +// std::string path=mail->translatePath(std::string pathName); +// +// bool ok=mail->createFolder(const mail::folder *, bool createDirectory); +// +// mail::folder *f=mail->createFolder(const mail::folder *parent, +// string Name, bool createDirectory); +// +// mail::folder *f=mail->renameFolder(mail::folder *oldFolder, +// mail::folder *newParent, +// string newName); +// +// bool ok=mail->deleteFolder(f, bool deleteDirectory); +// +// mail::ACCOUNT::FolderInfo folderInfo; +// +// bool ok=readFolderInfo(f, folderInfo); +// +// bool ok=mail->openFolder(f, snapshot); +// +// size_t nMsgs=mail->getFolderIndexSize(); +// +// mail::messageInfo getFolderIndexInfo(size_t msgNum); +// +// void getFolderKeywordInfo(size_t msgNum, set<string> &keywords); +// +// mail::messageInfo msgInfo=mail->readFolderInfo(size_t msgNum); +// +// vector<mail::xenvelope> envelopeList; +// vector<mail::mimestruct> structureList; +// vector<size_t> msgNumVector; +// +// bool ok=getMessageEnvelope(msgNumVector, envelopeList); +// +// bool ok=getMessageStructure(msgNumVector, structureList); +// +// mail::xenvelope env=getMessageEnvelope(size_t messageNum); +// mail::mimestruct str=getMessageStructure(size_t messageNum); +// +// bool ok=mail->saveFolderIndexInfo(msgNum, msgInfo); +// +// bool updateKeywords(const std::vector<size_t> &messages, +// const std::set<std::string> &keywords, +// bool setOrChange, +// // false: set, true: see changeTo +// bool changeTo); +// bool updateKeywords(const std::vector<size_t> &messages, +// const std::vector<std::string> &keywords, +// bool setOrChange, +// // false: set, true: see changeTo +// bool changeTo); +// bool updateKeywords(const std::vector<size_t> &messages, +// const std::list<std::string> &keywords, +// bool setOrChange, +// // false: set, true: see changeTo +// bool changeTo); +// bool ok=mail->updateFolderIndexInfo(); +// +// bool ok=mail->checkNewMail(); +// +// bool flag=mail->folderModified(); +// +// bool ok=mail->removeMessages(msgNumVector); +// +// bool ok=mail->updateFolderIndexFlags(msgNumVector, bool doFlip, +// bool enableDisable, +// mail::messageInfo &flags); +// +// bool ok=mail->copyMessagesTo(msgNumVector, mail::folder *copyTo); + +// bool ok=mail->moveMessagesTo(msgNumVector, mail::folder *copyTo); +// +// bool ok=mail->searchMessages(const mail::searchParams &searchInfo, +// msgNumVector); +// +// class myStore : public mail::ACCOUNT::Store { +// +// .... +// +// } +// +// myStore myStoreCallback; +// +// bool ok=mail->getMessageContent(msgNumVector, bool peek, +// bool getHeaders, +// bool getContent, myStoreCallback); +// +// bool ok=mail->getMessageContent(msgNum, bool peek, +// mail::mimestruct &part, +// bool getHeaders, +// bool getContent, myStoreCallback); +// +// bool ok=mail->getMessageContentDecoded(msgNum, bool peek, +// mail::mimestruct &part, +// myStoreCallback); +// +// class getMyMessageContents : public mail::addMessagePull { +// +// .... +// +// } +// +// getMyMessageContents contents; +// +// bool ok=mail->addMessage(f, contents); +// +// mail::smtpInfo sendMessageInfo; +// +// bool sent=mail->send(sendMessageInfo, folder, contents); +// +// string errmsg=mail->errmsg; +// +// bool disconnected=mail->disconnected(); + +LIBMAIL_START + +class account; + +class xenvelope; +class mimestruct; +class addMessagePull; + +class ACCOUNT { + +public: + class callback; + class disconnectCallback; + class readFoldersCallback; + class readMessageCallback; + class SearchCallback; + + class Progress { + bool reportingProgress; + ACCOUNT *myAccount; + public: + friend class ACCOUNT; + friend class ACCOUNT::callback; + + Progress(ACCOUNT *); + virtual void operator()(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal)=0; + virtual ~Progress(); + }; + + friend class Progress; + +private: + account *ptr; + + Progress *currentProgressReporter; + + class disconnectCallback *imap_disconnect; + + bool eventloop(callback &); + + class FolderCallback : public mail::callback::folder { + public: + ACCOUNT *account; + + FolderCallback(); + ~FolderCallback(); + private: + void newMessages(); + void messagesRemoved(std::vector< std::pair<size_t, + size_t> > &); + void messageChanged(size_t n); + } folderCallback; + + bool folderModifiedFlag; + +public: + + class FolderList { + std::vector<class mail::folder *> folders; + + public: + FolderList(); + ~FolderList(); + + FolderList(const FolderList &); + FolderList &operator=(const FolderList &); + + void append(const mail::folder *); + + typedef std::vector<class mail::folder *>::iterator iterator; + + iterator begin() + { + return iterator(folders.begin()); + } + + iterator end() + { + return iterator(folders.end()); + } + + size_t size() const + { + return folders.size(); + } + + const mail::folder *operator[](size_t n) const + { + return n < 0 || n >= folders.size() + ? NULL:folders[n]; + } + }; + + class Store { + public: + Store(); + virtual ~Store(); + + virtual void store(size_t, std::string)=0; + }; + + class FolderInfo { + public: + FolderInfo(); + ~FolderInfo(); + + size_t messageCount; + size_t unreadCount; + bool fastInfo; + }; + + std::string errmsg; + + ACCOUNT(); + virtual ~ACCOUNT(); + + std::string getErrmsg() { return errmsg; } + + bool login( mail::account::openInfo openInfoArg); + + void logout(); + + bool getTopLevelFolders(FolderList &list); + bool getSubFolders(const mail::folder *folder, FolderList &list); + bool getParentFolder(const mail::folder *folder, FolderList &list); + mail::folder *getFolderFromString(std::string); + mail::folder *getFolderFromPath(std::string); + std::string translatePath(std::string); + + bool createFolder(const mail::folder *, bool); + + mail::folder *createFolder(const mail::folder *, std::string, bool); + + mail::folder *renameFolder(const mail::folder *, + const mail::folder *, std::string); + + bool deleteFolder(const mail::folder *, bool); + + bool openFolder(const mail::folder *folder, + mail::snapshot *restoreSnapshot); + bool readFolderInfo(const mail::folder *folder, FolderInfo &info); + + size_t getFolderIndexSize(); + class mail::messageInfo getFolderIndexInfo(size_t); + + void getFolderKeywordInfo(size_t msgNum, + std::set<std::string> &keywords); + + bool saveFolderIndexInfo(size_t messageNum, + const mail::messageInfo &newInfo); + + bool updateKeywords(const std::vector<size_t> &messages, + const std::set<std::string> &keywords, + bool setOrChange, + // false: set, true: see changeTo + bool changeTo); + bool updateKeywords(const std::vector<size_t> &messages, + const std::vector<std::string> &keywords, + bool setOrChange, + // false: set, true: see changeTo + bool changeTo); + bool updateKeywords(const std::vector<size_t> &messages, + const std::list<std::string> &keywords, + bool setOrChange, + // false: set, true: see changeTo + bool changeTo); + + + bool updateFolderIndexInfo(); + + bool removeMessages(const std::vector<size_t> &messages); + + bool updateFolderIndexFlags(const std::vector<size_t> &messages, + bool doFlip, + bool enableDisable, + const mail::messageInfo &flags); + + bool copyMessagesTo(const std::vector<size_t> &messages, + mail::folder *copyTo); + + bool moveMessagesTo(const std::vector<size_t> &messages, + mail::folder *copyTo); + + + bool checkNewMail(); + + bool getMessageEnvelope(const std::vector<size_t> &messages, + std::vector<xenvelope> &envelopes); + bool getMessageStructure(const std::vector<size_t> &messages, + std::vector<mimestruct> &structures); + xenvelope getMessageEnvelope(size_t messageNum); + mimestruct getMessageStructure(size_t messageNum); + + bool searchMessages(const searchParams &searchInfo, + std::vector<size_t> &messageList); + + bool getMessageContent(const std::vector<size_t> &messages, + bool peek, + enum mail::readMode getType, + Store &contentCallback); + bool getMessageContent(size_t messageNum, + bool peek, + const mimestruct &messagePart, + enum mail::readMode getType, + Store &contentCallback); + bool getMessageContentDecoded(size_t messageNum, + bool peek, + const mimestruct &messagePart, + Store &callback); + + bool addMessage(const mail::folder *folder, + addMessagePull &message); + + bool send(const class mail::smtpInfo &messageInfo, + const mail::folder *saveFolder, + addMessagePull &message); + + bool getMyRights(const mail::folder *folder, std::string &rights); + bool getRights(const mail::folder *folder, + std::list<std::pair<std::string, std::string> > + &rights); + + bool setRights(const mail::folder *folder, + std::string &errorIdentifier, + std::vector<std::string> &errorRights, + std::string identifier, + std::string rights); + bool delRights(const mail::folder *folder, + std::string &errorIdentifier, + std::vector<std::string> &errorRights, + std::string identifier); + + bool folderModified(); + bool disconnected(); + void disconnected(std::string errmsg); +}; +LIBMAIL_END + +#endif diff --git a/libmail/testsuite.C b/libmail/testsuite.C new file mode 100644 index 0000000..4272459 --- /dev/null +++ b/libmail/testsuite.C @@ -0,0 +1,90 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "nntpnewsrc.H" + +#include <iostream> + +using namespace std; + +static void test1() +{ + mail::nntp::newsrc n; + + n.newsgroupname="misc.test"; + + n.read(2); + n.read(4); + n.read(8); + n.read(9); + + n.read(12); + n.read(11); + + cout << (string)n << endl; + + n.read(1); + n.read(13); + + cout << (string)n << endl; + + n.read(3); + + cout << (string)n << endl; + + n.read(1); + n.read(4); + n.read(11); + n.read(13); + n.read(12); + + cout << (string)n << endl; +} + +static void test2() +{ + mail::nntp::newsrc n("control! 1-5,10-15,20-25"); + + cout << (string)n << endl; + + n.unread(1); + + cout << (string)n << endl; + + n.unread(5); + + cout << (string)n << endl; + + n.unread(12); + + cout << (string)n << endl; + + n.unread(10); + + cout << (string)n << endl; + + n.unread(11); + + cout << (string)n << endl; + n.unread(15); + n.unread(14); + n.unread(13); + cout << (string)n << endl; + + n.unread(1); + n.unread(5); + n.unread(10); + n.unread(19); + n.unread(26); + cout << (string)n << endl; +} + +int main() +{ + test1(); + test2(); + exit(0); +} diff --git a/libmail/testsuite.txt b/libmail/testsuite.txt new file mode 100644 index 0000000..c253e99 --- /dev/null +++ b/libmail/testsuite.txt @@ -0,0 +1,12 @@ +misc.test: 2,4,8-9,11-12 +misc.test: 1-2,4,8-9,11-13 +misc.test: 1-4,8-9,11-13 +misc.test: 1-4,8-9,11-13 +control! 1-5,10-15,20-25 +control! 2-5,10-15,20-25 +control! 2-4,10-15,20-25 +control! 2-4,10-11,13-15,20-25 +control! 2-4,11,13-15,20-25 +control! 2-4,13-15,20-25 +control! 2-4,20-25 +control! 2-4,20-25 diff --git a/libmail/tmpaccount.C b/libmail/tmpaccount.C new file mode 100644 index 0000000..21d3e2e --- /dev/null +++ b/libmail/tmpaccount.C @@ -0,0 +1,417 @@ +/* +** Copyright 2003-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "tmpaccount.H" +#include "driver.H" +#include "file.H" +#include "copymessage.H" +#include "search.H" +#include <errno.h> +#include <sys/stat.h> +#include <cstring> + +using namespace std; + +LIBMAIL_START + +static bool open_tmp(mail::account *&accountRet, + mail::account::openInfo &oi, + mail::callback &callback, + mail::callback::disconnect &disconnectCallback) +{ + if (oi.url == "tmp:") + { + accountRet=new mail::tmpaccount(disconnectCallback); + + if (accountRet) + callback.success("Ok."); + else + callback.fail(strerror(errno)); + return true; + } + return false; +} + +static bool remote_tmp(string url, bool &flag) +{ + if (url == "tmp:") + { + flag=false; + return true; + } + + return false; +} + +driver tmp_driver= { &open_tmp, &remote_tmp }; + +LIBMAIL_END + +mail::tmpaccount::tmpaccount(callback::disconnect &disconnect_callbackArg) + : account(disconnect_callbackArg), + disconnect_callback(&disconnect_callbackArg), + folder_callback(NULL), f(NULL), rfc2045p(NULL) +{ +} + +void mail::tmpaccount::disconnect(const char *reason) +{ + callback::disconnect *d=disconnect_callback; + + disconnect_callback=NULL; + + if (d) + d->disconnected(reason); +} + +mail::tmpaccount::~tmpaccount() +{ + disconnect("Disconnected."); + if (f) + fclose(f); + if (rfc2045p) + rfc2045_free(rfc2045p); +} + + +void mail::tmpaccount::resumed() +{ +} + +void mail::tmpaccount::handler(vector<pollfd> &fds, int &ioTimeout) +{ +} + +void mail::tmpaccount::logout(mail::callback &callback) +{ + disconnect(""); + callback.success("Ok."); +} + +void mail::tmpaccount::checkNewMail(callback &callback) +{ + callback.success("Ok."); +} + +bool mail::tmpaccount::hasCapability(std::string capability) +{ + if (capability == LIBMAIL_SINGLEFOLDER) + return true; + return false; +} + +string mail::tmpaccount::getCapability(std::string capability) +{ + if (capability == LIBMAIL_SERVERTYPE) + return "tmp"; + + if (capability == LIBMAIL_SERVERDESCR) + return "Temporary account"; + + return ""; +} + +mail::folder *mail::tmpaccount::folderFromString(std::string path) +{ + return new folder(this); +} + +void mail::tmpaccount::readTopLevelFolders(callback::folderList &callback1, + callback &callback2) +{ + folder dummyFolder(this); + + vector<const mail::folder *> dummy; + + dummy.push_back(&dummyFolder); + + callback1.success(dummy); + callback2.success("Ok."); +} + +void mail::tmpaccount::findFolder(std::string path, + callback::folderList &callback1, + callback &callback2) +{ + folder dummyFolder(this); + + vector<const mail::folder *> dummy; + + dummy.push_back(&dummyFolder); + + callback1.success(dummy); + callback2.success("Ok."); +} + +std::string mail::tmpaccount::translatePath(std::string path) +{ + return path; +} + +void mail::tmpaccount::readMessageAttributes(const std::vector<size_t> + &messages, + MessageAttributes attributes, + callback::message &callback) +{ + genericAttributes(this, this, messages, attributes, callback); +} + +void mail::tmpaccount::readMessageContent(const std::vector<size_t> &messages, + bool peek, + enum mail::readMode readType, + callback::message &callback) +{ + genericReadMessageContent(this, this, messages, peek, + readType, callback); +} + +void mail::tmpaccount::readMessageContent(size_t messageNum, + bool peek, + const class mimestruct &msginfo, + enum mail::readMode readType, + callback::message &callback) +{ + genericReadMessageContent(this, this, messageNum, peek, + msginfo, readType, callback); +} + +void mail::tmpaccount::readMessageContentDecoded(size_t messageNum, + bool peek, + const class mimestruct + &msginfo, + callback::message &callback) +{ + genericReadMessageContentDecoded(this, this, + messageNum, peek, msginfo, callback); +} + +size_t mail::tmpaccount::getFolderIndexSize() +{ + return f ? 1:0; +} + +mail::messageInfo mail::tmpaccount::getFolderIndexInfo(size_t n) +{ + if (n == 0 && f) + return fInfo; + + return mail::messageInfo(); +} + +void mail::tmpaccount::saveFolderIndexInfo(size_t messageNum, + const messageInfo &msgInfo, + callback &callback) +{ + if (messageNum == 0 && f) + { + messageInfo n=msgInfo; + + n.uid=fInfo.uid; + fInfo=n; + + if (folder_callback) + folder_callback->messageChanged(0); + } + + callback.success("Ok."); +} + +void mail::tmpaccount::updateFolderIndexFlags(const std::vector<size_t> + &messages, + bool doFlip, + bool enableDisable, + const messageInfo &flags, + callback &callback) +{ + vector<size_t>::const_iterator b, e; + + b=messages.begin(); + e=messages.end(); + + bool dirty=false; + + while (b != e) + { + size_t i= *b++; + + if (i == 0 && f) + { +#define DOFLAG(dummy, field, dummy2) \ + if (flags.field) \ + { \ + fInfo.field=\ + doFlip ? !fInfo.field\ + : enableDisable; \ + } + + LIBMAIL_MSGFLAGS; +#undef DOFLAG + dirty=true; + } + } + + if (dirty && folder_callback) + folder_callback->messageChanged(0); + + callback.success("Ok."); +} + +void mail::tmpaccount::updateFolderIndexInfo(callback &cb) +{ + if (f && fInfo.deleted) + { + fclose(f); + f=NULL; + + vector< pair<size_t, size_t> > dummy; + + dummy.push_back( make_pair( (size_t)0, (size_t)0)); + if (folder_callback) + folder_callback->messagesRemoved(dummy); + } + cb.success("Ok."); +} + +void mail::tmpaccount::removeMessages(const std::vector<size_t> &messages, + callback &cb) +{ + genericRemoveMessages(this, messages, cb); +} + +void mail::tmpaccount::copyMessagesTo(const std::vector<size_t> &messages, + mail::folder *copyTo, + callback &callback) +{ + mail::copyMessages::copy(this, messages, copyTo, callback); +} + +void mail::tmpaccount::searchMessages(const searchParams &searchInfo, + searchCallback &callback) +{ + mail::searchMessages::search(callback, searchInfo, this); +} + +void mail::tmpaccount::genericMessageRead(std::string uid, + size_t messageNumber, + bool peek, + mail::readMode readType, + mail::callback::message &callback) +{ + if (f && fInfo.uid == uid) + { + if (fseek(f, 0L, SEEK_END) < 0 || fseek(f, 0L, SEEK_SET) < 0) + { + callback.fail(strerror(errno)); + return; + } + + mail::file rf(fileno(f), "r"); + + if (!rf) + { + callback.fail(strerror(errno)); + return; + } + + rf.genericMessageRead(this, 0, readType, callback); + + if (ferror(rf)) + { + callback.fail(strerror(errno)); + return; + } + } + + if (!peek) + genericMarkRead(0); + + callback.success("Ok."); +} + +void mail::tmpaccount::genericMessageSize(std::string uid, + size_t messageNumber, + mail::callback::message &callback) +{ + if (f && fInfo.uid == uid) + { + struct stat stat_buf; + + if (fstat(fileno(f), &stat_buf) == 0) + { + callback.messageSizeCallback(0, stat_buf.st_size); + callback.success("Ok."); + return; + } + + callback.fail(strerror(errno)); + } + else + callback.success("Ok."); +} + +void mail::tmpaccount::genericGetMessageFd(std::string uid, + size_t messageNumber, + bool peek, + int &fdRet, + mail::callback &callback) +{ + struct rfc2045 *dummy; + + genericGetMessageFdStruct(uid, messageNumber, peek, fdRet, dummy, + callback); +} + +void mail::tmpaccount::genericGetMessageStruct(std::string uid, + size_t messageNumber, + struct rfc2045 *&structRet, + mail::callback &callback) +{ + int dummy; + + genericGetMessageFdStruct(uid, messageNumber, true, dummy, structRet, + callback); +} + +bool mail::tmpaccount::genericCachedUid(std::string uid) +{ + return f && uid == fInfo.uid; +} + +void mail::tmpaccount::genericGetMessageFdStruct(std::string uid, + size_t messageNumber, + bool peek, + int &fdRet, + struct rfc2045 *&structret, + mail::callback &callback) +{ + if (f && uid == fInfo.uid) + { + if (fseek(f, 0L, SEEK_END) < 0 || fseek(f, 0L, SEEK_SET) < 0) + { + callback.fail(strerror(errno)); + return; + } + fdRet=fileno(f); + structret=rfc2045p; + if (!peek) + genericMarkRead(0); + + callback.success("Ok."); + return; + } + + callback.fail(strerror(ENOENT)); +} + +void mail::tmpaccount::genericMarkRead(size_t messageNumber) +{ + + if (f && fInfo.unread) + { + fInfo.unread=false; + if (folder_callback) + folder_callback->messageChanged(0); + } +} diff --git a/libmail/tmpaccount.H b/libmail/tmpaccount.H new file mode 100644 index 0000000..661bd5f --- /dev/null +++ b/libmail/tmpaccount.H @@ -0,0 +1,208 @@ +/* +** Copyright 2003-2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#ifndef libmail_tmpaccount_H +#define libmail_tmpaccount_H + +#include "libmail_config.h" +#include "mail.H" +#include "generic.H" +#include "addmessage.H" + +#include "rfc2045/rfc2045.h" +#include <stdio.h> + +LIBMAIL_START + +// +// The tmp: driver - a driver that uses a temporary file that holds exactly +// one message. +// The driver provides a single folder, INBOX. Adding a message to the +// folder automatically expunges the previous message. +// + +class tmpaccount : public account, public generic { + + void resumed(); + void handler(std::vector<pollfd> &fds, int &timeout); + + callback::disconnect *disconnect_callback; + callback::folder *folder_callback; + FILE *f; + struct rfc2045 *rfc2045p; + messageInfo fInfo; + + void disconnect(const char *); + + class folder : public mail::folder { + + tmpaccount *account; + + static void na(callback &); + public: + folder(tmpaccount *); + ~folder(); + + bool isParentOf(std::string path) const; + + void hasMessages(bool); + void hasSubFolders(bool); + void readFolderInfo( callback::folderInfo + &callback1, + callback &callback2) const; + void getParentFolder(callback::folderList &callback1, + callback &callback2) const; + void readSubFolders( callback::folderList &callback1, + callback &callback2) const; + + mail::addMessage *addMessage(callback &callback) const; + + void createSubFolder(std::string name, bool isDirectory, + callback::folderList + &callback1, + callback &callback2) const; + void create(bool isDirectory, + callback &callback) const; + + void destroy(callback &callback, bool destroyDir) const; + + void renameFolder(const mail::folder *newParent, + std::string newName, + callback::folderList &callback1, + callback &callback2) const; + + folder *clone() const; + + std::string toString() const; + + void open(callback &openCallback, + snapshot *restoreSnapshot, + callback::folder &folderCallback) const; + + void sameServerAsHelperFunc() const; + + std::string getName() const; + std::string getPath() const; + bool hasMessages() const; + bool hasSubFolders() const; + }; + + class add : public addMessage { + + FILE *newFile; + struct rfc2045 *rfc2045p; + + tmpaccount *account; + mail::callback &cb; + public: + add(tmpaccount *accountArg, mail::callback &cbArg); + ~add(); + virtual void saveMessageContents(std::string); + virtual void go(); + virtual void fail(std::string errmsg); + }; + +public: + + friend class mail::tmpaccount::folder; + friend class mail::tmpaccount::add; + + tmpaccount(callback::disconnect &disconnect_callbackArg); + virtual ~tmpaccount(); + + void logout(callback &callback); + void checkNewMail(callback &callback); + + bool hasCapability(std::string capability); + std::string getCapability(std::string capability); + mail::folder *folderFromString(std::string); + void readTopLevelFolders(callback::folderList &callback1, + callback &callback2); + void findFolder(std::string path, + callback::folderList &callback1, + callback &callback2); + std::string translatePath(std::string path); + + + void readMessageAttributes(const std::vector<size_t> &messages, + MessageAttributes attributes, + callback::message &callback); + void readMessageContent(const std::vector<size_t> &messages, + bool peek, + enum mail::readMode readType, + callback::message &callback); + + void readMessageContent(size_t messageNum, + bool peek, + const class mimestruct &msginfo, + enum mail::readMode readType, + callback::message &callback); + + void readMessageContentDecoded(size_t messageNum, + bool peek, + const class mimestruct + &msginfo, + callback::message &callback); + size_t getFolderIndexSize(); + + messageInfo getFolderIndexInfo(size_t); + void saveFolderIndexInfo(size_t messageNum, + const messageInfo &msgInfo, + callback &callback); + void updateFolderIndexFlags(const std::vector<size_t> &messages, + bool doFlip, + bool enableDisable, + const messageInfo &flags, + callback &callback); + + void updateFolderIndexInfo(callback &); + void removeMessages(const std::vector<size_t> &messages, + callback &cb); + void copyMessagesTo(const std::vector<size_t> &messages, + mail::folder *copyTo, + callback &callback); + void searchMessages(const searchParams &searchInfo, + searchCallback &callback); + + + + + void genericMessageRead(std::string uid, + size_t messageNumber, + bool peek, + mail::readMode readType, + mail::callback::message &callback); + + void genericMessageSize(std::string uid, + size_t messageNumber, + mail::callback::message &callback); + + void genericGetMessageFd(std::string uid, + size_t messageNumber, + bool peek, + int &fdRet, + mail::callback &callback); + + void genericGetMessageStruct(std::string uid, + size_t messageNumber, + struct rfc2045 *&structRet, + mail::callback &callback); + + void genericGetMessageFdStruct(std::string uid, + size_t messageNumber, + bool peek, + int &fdRet, + struct rfc2045 *&structret, + + mail::callback &callback); + + bool genericCachedUid(std::string uid); + + void genericMarkRead(size_t messageNumber); +}; + +LIBMAIL_END + +#endif diff --git a/libmail/tmpaccountadd.C b/libmail/tmpaccountadd.C new file mode 100644 index 0000000..cf6b805 --- /dev/null +++ b/libmail/tmpaccountadd.C @@ -0,0 +1,79 @@ +/* +** Copyright 2003-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "tmpaccount.H" +#include <errno.h> +#include <cstring> + +using namespace std; + +mail::tmpaccount::add::add(tmpaccount *accountArg, mail::callback &cbArg) + : addMessage(accountArg), newFile(tmpfile()), + rfc2045p(rfc2045_alloc()), account(accountArg), + cb(cbArg) +{ +} + +mail::tmpaccount::add::~add() +{ + if (newFile) + fclose(newFile); + if (rfc2045p) + rfc2045_free(rfc2045p); +} + +void mail::tmpaccount::add::saveMessageContents(string s) +{ + if (newFile) + if (fwrite(&s[0], s.size(), 1, newFile) != 1) + ; // Ignore gcc warning + if (rfc2045p) + rfc2045_parse(rfc2045p, &s[0], s.size()); +} + +void mail::tmpaccount::add::fail(string errmsg) +{ + cb.fail(errmsg); + delete this; +} + +void mail::tmpaccount::add::go() +{ + if (!checkServer()) + return; + + if (!newFile || fflush(newFile) < 0 || ferror(newFile) || !rfc2045p) + { + fail(strerror(errno)); + return; + } + + rfc2045_parse_partial(rfc2045p); + + if (account->f) + { + fclose(account->f); + account->f=NULL; + vector< pair<size_t, size_t> > dummy; + + dummy.push_back( make_pair( (size_t)0, (size_t)0)); + if (account->folder_callback) + account->folder_callback->messagesRemoved(dummy); + } + + if (account->rfc2045p) + rfc2045_free(account->rfc2045p); + + account->rfc2045p=rfc2045p; + account->f=newFile; + rfc2045p=NULL; + newFile=NULL; + + if (account->folder_callback) + account->folder_callback->newMessages(); + + cb.success("Ok."); + delete this; +} diff --git a/libmail/tmpaccountfolder.C b/libmail/tmpaccountfolder.C new file mode 100644 index 0000000..5c2649b --- /dev/null +++ b/libmail/tmpaccountfolder.C @@ -0,0 +1,165 @@ +/* +** Copyright 2003, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "tmpaccount.H" +#include <errno.h> +#include <vector> +#include <cstring> + +using namespace std; + +void mail::tmpaccount::folder::na(callback &cb) +{ + cb.fail("Not implemented."); +} + +mail::tmpaccount::folder::folder(tmpaccount *accountArg) + : mail::folder(accountArg), account(accountArg) +{ +} + +mail::tmpaccount::folder::~folder() +{ +} + +bool mail::tmpaccount::folder::isParentOf(std::string path) const +{ + return false; +} + +void mail::tmpaccount::folder::hasMessages(bool dummy) +{ +} + +void mail::tmpaccount::folder::hasSubFolders(bool dummy) +{ +} + +void mail::tmpaccount::folder::readFolderInfo( callback::folderInfo + &callback1, + callback &callback2) const +{ + if (isDestroyed(callback2)) + return; + + callback1.messageCount=0; + callback1.unreadCount=0; + + if (account->f) + { + ++callback1.messageCount; + if (account->fInfo.unread) + ++callback1.unreadCount; + } + callback1.success(); + callback2.success("Ok"); +} + +void mail::tmpaccount::folder::getParentFolder(callback::folderList &callback1, + callback &callback2) const +{ + na(callback2); +} + +void mail::tmpaccount::folder::readSubFolders( callback::folderList &callback1, + callback &callback2) const +{ + vector<const mail::folder *> dummy; + + callback1.success(dummy); + callback2.success("Ok"); +} + +mail::addMessage *mail::tmpaccount::folder::addMessage(callback &callback) + const +{ + if (isDestroyed(callback)) + return NULL; + + add *a=new add(account, callback); + + if (!a) + callback.fail(strerror(errno)); + + return a; +} + +void mail::tmpaccount::folder::createSubFolder(std::string name, + bool isDirectory, + callback::folderList + &callback1, + callback &callback2) const +{ + na(callback2); +} + +void mail::tmpaccount::folder::create(bool isDirectory, + callback &callback) const +{ + na(callback); +} + +void mail::tmpaccount::folder::destroy(callback &callback, + bool destroyDir) const +{ + na(callback); +} + +void mail::tmpaccount::folder::renameFolder(const mail::folder *newParent, + string newName, + callback::folderList &callback1, + callback &callback2) const +{ + na(callback2); +} + +mail::tmpaccount::folder *mail::tmpaccount::folder::clone() const +{ + return new folder(account); +} + +string mail::tmpaccount::folder::toString() const +{ + return "INBOX"; +} + +void mail::tmpaccount::folder::open(callback &openCallback, + snapshot *restoreSnapshot, + callback::folder &folderCallback) const +{ + if (isDestroyed(openCallback)) + return; + + if (account->f) + fclose(account->f); + account->f=NULL; + + account->folder_callback= &folderCallback; + openCallback.success("Ok."); +} + +void mail::tmpaccount::folder::sameServerAsHelperFunc() const +{ +} + +string mail::tmpaccount::folder::getName() const +{ + return "INBOX"; +} + +string mail::tmpaccount::folder::getPath() const +{ + return "INBOX"; +} + +bool mail::tmpaccount::folder::hasMessages() const +{ + return true; +} + +bool mail::tmpaccount::folder::hasSubFolders() const +{ + return false; +} |
