summaryrefslogtreecommitdiffstats
path: root/libmail/generic.C
diff options
context:
space:
mode:
Diffstat (limited to 'libmail/generic.C')
-rw-r--r--libmail/generic.C1997
1 files changed, 1997 insertions, 0 deletions
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.");
+}
+
+