/* ** 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 #include #include #include #include #include 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 account; // My account mail::generic *genericInterface; // Ditto mail::envelope envelope; mail::mimestruct structure; time_t arrivalDate; vector< pair > messages; vector< pair >::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 &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 &msgsArg, mail::account::MessageAttributes attributesArg, mail::callback::message *callbackArg) : account(accountArg), genericInterface(genericInterfaceArg), attributes(attributesArg), callback(callbackArg) { // Memorize all message numbers and uids vector::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_list; size_t err_index; address::fromString(value, address_list, err_index); envelope.references.clear(); vector
::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 &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 &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 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 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 > 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 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 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 &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 &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::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 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 acct; set 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 &messages, mail::callback *callbackArg); ~Remove(); void mkMessageList(vector &msgList); }; mail::generic::Remove::Remove(mail::account *acctArg, const std::vector &messages, mail::callback *callbackArg) : acct(acctArg), callback(callbackArg), success_func(&mail::generic::Remove::expunge) { vector::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 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 &msgList) { mail::account *p=acct; if (!p) return; size_t n=p->getFolderIndexSize(); size_t i; for (i=0; igetFolderIndexInfo(i).uid) > 0) msgList.push_back(i); } void mail::generic::genericRemoveMessages(mail::account *account, const std::vector &messages, callback &cb) { vector msgToDo; { set msgSet; msgSet.insert(messages.begin(), messages.end()); size_t i, n=account->getFolderIndexSize(); for (i=0; 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 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 acct; set msgs; mail::callback *callback; public: Move(mail::account *acctArg, const vector &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 &messages, mail::callback *callbackArg) : acct(acctArg), callback(callbackArg) { vector::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 msgList; size_t n=acct->getFolderIndexSize(); size_t i; for (i=0; igetFolderIndexInfo(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 &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 &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::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 &messages, const set &keywords, bool setOrChange, // false: set, true: see changeTo bool changeTo, mail::callback::folder *folderCallback, mail::generic *generic, mail::callback &cb) { vector::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."); }