summaryrefslogtreecommitdiffstats
path: root/libmail
diff options
context:
space:
mode:
Diffstat (limited to 'libmail')
-rw-r--r--libmail/.gitignore1
-rw-r--r--libmail/Makefile.am189
-rw-r--r--libmail/addmessage.C148
-rw-r--r--libmail/addmessage.H139
-rw-r--r--libmail/addmessageimport.C194
-rw-r--r--libmail/addmessageremoveattachments.C404
-rw-r--r--libmail/addressbook.C296
-rw-r--r--libmail/addressbook.H159
-rw-r--r--libmail/addressbookadd.C266
-rw-r--r--libmail/addressbookadd.H65
-rw-r--r--libmail/addressbookget.C255
-rw-r--r--libmail/addressbookget.H69
-rw-r--r--libmail/addressbookopen.C98
-rw-r--r--libmail/addressbookopen.H49
-rw-r--r--libmail/addressbooksearch.C82
-rw-r--r--libmail/addressbooksearch.H48
-rw-r--r--libmail/attachments.C631
-rw-r--r--libmail/attachments.H147
-rw-r--r--libmail/autodecoder.C67
-rw-r--r--libmail/autodecoder.H62
-rw-r--r--libmail/autodeps.C26
-rw-r--r--libmail/base64.C198
-rw-r--r--libmail/base64.H91
-rw-r--r--libmail/configure.in132
-rw-r--r--libmail/copymessage.C455
-rw-r--r--libmail/copymessage.H188
-rw-r--r--libmail/decoder.C16
-rw-r--r--libmail/decoder.H31
-rw-r--r--libmail/driver.C32
-rw-r--r--libmail/driver.H33
-rw-r--r--libmail/envelope.C34
-rw-r--r--libmail/envelope.H49
-rw-r--r--libmail/expungelist.C39
-rw-r--r--libmail/expungelist.H42
-rw-r--r--libmail/fd.C850
-rw-r--r--libmail/fd.H191
-rw-r--r--libmail/fdtls.C95
-rw-r--r--libmail/fdtls.H126
-rw-r--r--libmail/file.C191
-rw-r--r--libmail/file.H62
-rw-r--r--libmail/generic.C1997
-rw-r--r--libmail/generic.H243
-rw-r--r--libmail/genericdecode.C50
-rw-r--r--libmail/genericdecode.H54
-rw-r--r--libmail/headers.C319
-rw-r--r--libmail/headers.H219
-rw-r--r--libmail/imap.C1709
-rw-r--r--libmail/imap.H526
-rw-r--r--libmail/imapacl.C925
-rw-r--r--libmail/imapacl.H115
-rw-r--r--libmail/imapfetchhandler.C147
-rw-r--r--libmail/imapfetchhandler.H74
-rw-r--r--libmail/imapfolder.C3498
-rw-r--r--libmail/imapfolder.H140
-rw-r--r--libmail/imapfolders.C1637
-rw-r--r--libmail/imapfolders.H108
-rw-r--r--libmail/imaphandler.C354
-rw-r--r--libmail/imaphandler.H86
-rw-r--r--libmail/imaphmac.C45
-rw-r--r--libmail/imaphmac.H44
-rw-r--r--libmail/imapidle.C197
-rw-r--r--libmail/imapidle.H60
-rw-r--r--libmail/imaplisthandler.C498
-rw-r--r--libmail/imaplisthandler.H94
-rw-r--r--libmail/imaplogin.C1128
-rw-r--r--libmail/imaplogin.H47
-rw-r--r--libmail/imaplogout.C143
-rw-r--r--libmail/imapparsefmt.C152
-rw-r--r--libmail/imapparsefmt.H63
-rw-r--r--libmail/imapstatushandler.C230
-rw-r--r--libmail/imapstatushandler.H43
-rw-r--r--libmail/logininfo.C76
-rw-r--r--libmail/logininfo.H113
-rw-r--r--libmail/mail.C820
-rw-r--r--libmail/mail.H1027
-rw-r--r--libmail/maildir.C1718
-rw-r--r--libmail/maildir.H251
-rw-r--r--libmail/maildiradd.C230
-rw-r--r--libmail/maildiradd.H48
-rw-r--r--libmail/maildirfolder.C1003
-rw-r--r--libmail/maildirfolder.H108
-rw-r--r--libmail/mailtool.C1340
-rw-r--r--libmail/mbox.C1530
-rw-r--r--libmail/mbox.H470
-rw-r--r--libmail/mboxadd.C243
-rw-r--r--libmail/mboxadd.H71
-rw-r--r--libmail/mboxexpunge.C205
-rw-r--r--libmail/mboxexpunge.H43
-rw-r--r--libmail/mboxfolder.C860
-rw-r--r--libmail/mboxgetmessage.C195
-rw-r--r--libmail/mboxgetmessage.H49
-rw-r--r--libmail/mboxlock.C174
-rw-r--r--libmail/mboxlock.H67
-rw-r--r--libmail/mboxmagictag.C185
-rw-r--r--libmail/mboxmagictag.H57
-rw-r--r--libmail/mboxmultilock.C302
-rw-r--r--libmail/mboxmultilock.H126
-rw-r--r--libmail/mboxopen.C282
-rw-r--r--libmail/mboxopen.H63
-rw-r--r--libmail/mboxread.C165
-rw-r--r--libmail/mboxread.H43
-rw-r--r--libmail/mboxsighandler.C80
-rw-r--r--libmail/mboxsighandler.H47
-rw-r--r--libmail/mimetypes.C86
-rw-r--r--libmail/mimetypes.H42
-rw-r--r--libmail/misc.H54
-rw-r--r--libmail/namespace.H13
-rw-r--r--libmail/nntp.C1489
-rw-r--r--libmail/nntp.H336
-rw-r--r--libmail/nntpadd.C70
-rw-r--r--libmail/nntpadd.H39
-rw-r--r--libmail/nntpcache.C86
-rw-r--r--libmail/nntpcache.H51
-rw-r--r--libmail/nntpchecknew.C214
-rw-r--r--libmail/nntpchecknew.H54
-rw-r--r--libmail/nntpfetch.C186
-rw-r--r--libmail/nntpfetch.H74
-rw-r--r--libmail/nntpfolder.C554
-rw-r--r--libmail/nntpfolder.H100
-rw-r--r--libmail/nntpgroup.C91
-rw-r--r--libmail/nntpgroup.H56
-rw-r--r--libmail/nntpgroupinfo.C82
-rw-r--r--libmail/nntpgroupinfo.H40
-rw-r--r--libmail/nntpgroupopen.C331
-rw-r--r--libmail/nntpgroupopen.H74
-rw-r--r--libmail/nntplistactive.C224
-rw-r--r--libmail/nntplistactive.H63
-rw-r--r--libmail/nntplogin.C212
-rw-r--r--libmail/nntplogin.H66
-rw-r--r--libmail/nntplogin2.C102
-rw-r--r--libmail/nntplogin2.H42
-rw-r--r--libmail/nntplogout.C81
-rw-r--r--libmail/nntplogout.H40
-rw-r--r--libmail/nntpnewsrc.C215
-rw-r--r--libmail/nntpnewsrc.H43
-rw-r--r--libmail/nntppost.C157
-rw-r--r--libmail/nntppost.H59
-rw-r--r--libmail/nntpxover.C255
-rw-r--r--libmail/nntpxover.H62
-rw-r--r--libmail/nntpxpat.C231
-rw-r--r--libmail/nntpxpat.H77
-rw-r--r--libmail/objectmonitor.C39
-rw-r--r--libmail/objectmonitor.H107
-rw-r--r--libmail/poll.C17
-rw-r--r--libmail/pop3.C2845
-rw-r--r--libmail/pop3.H400
-rw-r--r--libmail/pop3folder.C140
-rw-r--r--libmail/pop3maildrop.C440
-rw-r--r--libmail/pop3maildrop.H61
-rw-r--r--libmail/qp.C144
-rw-r--r--libmail/qp.H34
-rw-r--r--libmail/rfc2047decode.C80
-rw-r--r--libmail/rfc2047decode.H77
-rw-r--r--libmail/rfc2047encode.C57
-rw-r--r--libmail/rfc2047encode.H52
-rw-r--r--libmail/rfcaddr.C442
-rw-r--r--libmail/rfcaddr.H154
-rw-r--r--libmail/runlater.C49
-rw-r--r--libmail/runlater.H46
-rw-r--r--libmail/search.C1198
-rw-r--r--libmail/search.H290
-rw-r--r--libmail/smap.C789
-rw-r--r--libmail/smap.H86
-rw-r--r--libmail/smapacl.C211
-rw-r--r--libmail/smapacl.H80
-rw-r--r--libmail/smapadd.C198
-rw-r--r--libmail/smapadd.H72
-rw-r--r--libmail/smapaddmessage.C122
-rw-r--r--libmail/smapaddmessage.H42
-rw-r--r--libmail/smapcopy.C95
-rw-r--r--libmail/smapcopy.H46
-rw-r--r--libmail/smapcreate.C153
-rw-r--r--libmail/smapcreate.H54
-rw-r--r--libmail/smapdelete.C52
-rw-r--r--libmail/smapdelete.H36
-rw-r--r--libmail/smapfetch.C141
-rw-r--r--libmail/smapfetch.H62
-rw-r--r--libmail/smapfetchattr.C621
-rw-r--r--libmail/smapfetchattr.H83
-rw-r--r--libmail/smapidle.C184
-rw-r--r--libmail/smapidle.H52
-rw-r--r--libmail/smaplist.C210
-rw-r--r--libmail/smaplist.H59
-rw-r--r--libmail/smapmsgrange.C140
-rw-r--r--libmail/smapmsgrange.H59
-rw-r--r--libmail/smapnewmail.C110
-rw-r--r--libmail/smapnewmail.H40
-rw-r--r--libmail/smapnoopexpunge.C124
-rw-r--r--libmail/smapnoopexpunge.H49
-rw-r--r--libmail/smapopen.C280
-rw-r--r--libmail/smapopen.H56
-rw-r--r--libmail/smapsearch.C239
-rw-r--r--libmail/smapsearch.H45
-rw-r--r--libmail/smapsendfolder.C222
-rw-r--r--libmail/smapsendfolder.H69
-rw-r--r--libmail/smapstatus.C86
-rw-r--r--libmail/smapstatus.H38
-rw-r--r--libmail/smapstore.C79
-rw-r--r--libmail/smapstore.H49
-rw-r--r--libmail/smtp.C1420
-rw-r--r--libmail/smtp.H239
-rw-r--r--libmail/smtpfolder.C282
-rw-r--r--libmail/smtpfolder.H109
-rw-r--r--libmail/smtpinfo.H43
-rw-r--r--libmail/snapshot.C25
-rw-r--r--libmail/snapshot.H69
-rw-r--r--libmail/sortfolders.C35
-rw-r--r--libmail/structure.C350
-rw-r--r--libmail/structure.H185
-rw-r--r--libmail/sync.C1176
-rw-r--r--libmail/sync.H401
-rw-r--r--libmail/testsuite.C90
-rw-r--r--libmail/testsuite.txt12
-rw-r--r--libmail/tmpaccount.C417
-rw-r--r--libmail/tmpaccount.H208
-rw-r--r--libmail/tmpaccountadd.C79
-rw-r--r--libmail/tmpaccountfolder.C165
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 &section;
+
+ 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 &sectionArg, bool a, bool b)
+ : section(sectionArg), somethingCopied(a),
+ somethingNotCopied(b) {}
+
+ ~mimeAction() {}
+ };
+
+ list<mimeAction> parseTree;
+
+ list<mimeAction>::iterator
+ computeToCopy(const mail::mimestruct &section,
+ 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 &section,
+ 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,
+ &currentFolder->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 &parametersArg,
+ 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 &parametersArg,
+ 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,
+ &param_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(&paramList,
+ 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;
+}