/** * @file operator-fetch.cc * @brief Implementation of the FETCH command * @author Andreas Aardal Hanssen * @date 2002-2005 */ #include "convert.h" #include "depot.h" #include "imapparser.h" #include "iodevice.h" #include "iofactory.h" #include "mailbox.h" #include "operators.h" #include "pendingupdates.h" #include "recursivedescent.h" #include "session.h" #include using namespace Binc; using std::string; using std::vector; constexpr auto ACCEPT = Parser::ParseResult::ACCEPT; constexpr auto ERROR = Parser::ParseResult::ERROR; constexpr auto REJECT = Parser::ParseResult::REJECT; namespace { void outputFlags(const Message &message) { bincClient << "FLAGS "; bincClient << "("; int flags = message.getStdFlags(); vector flagv; if (flags & Message::F_SEEN) flagv.push_back("\\Seen"); if (flags & Message::F_ANSWERED) flagv.push_back("\\Answered"); if (flags & Message::F_DELETED) flagv.push_back("\\Deleted"); if (flags & Message::F_DRAFT) flagv.push_back("\\Draft"); if (flags & Message::F_RECENT) flagv.push_back("\\Recent"); if (flags & Message::F_FLAGGED) flagv.push_back("\\Flagged"); for (auto k = flagv.begin(); k != flagv.end(); ++k) { if (k != flagv.begin()) bincClient << " "; bincClient << *k; } vector customFlags = message.getCustomFlags(); for (auto it = customFlags.begin(); it != customFlags.end(); ++it) { if (flagv.size() > 0 || it != customFlags.begin()) bincClient << " "; bincClient << *it; } bincClient << ")"; } } FetchOperator::FetchOperator() {} FetchOperator::~FetchOperator() {} const string FetchOperator::getName() const { return "FETCH"; } Session::State FetchOperator::getState() const { return Session::SELECTED; } Operator::ProcessResult FetchOperator::process(Depot &depot, Request &request) { Session &session = Session::getInstance(); bool updateFlags = false; Request req = request; Mailbox *mailbox = depot.getSelected(); // If this is a UID FETCH, check if the UID attribute is fetched. If // it is not, then add it to the list of fetch attributes. vector::const_iterator f_i; bool uidfetched = false; if (request.getUidMode()) { f_i = request.fatt.begin(); while (f_i != request.fatt.end()) { if ((*f_i).type == "UID") { uidfetched = true; break; } f_i++; } if (!uidfetched) { BincImapParserFetchAtt b; b.type = "UID"; req.fatt.push_back(b); } } // Convert macros ALL, FULL and FAST f_i = request.fatt.begin(); while (f_i != request.fatt.end()) { const string &type = (*f_i).type; if (type == "ALL") { req.fatt.push_back(BincImapParserFetchAtt("FLAGS")); req.fatt.push_back(BincImapParserFetchAtt("INTERNALDATE")); req.fatt.push_back(BincImapParserFetchAtt("RFC822.SIZE")); req.fatt.push_back(BincImapParserFetchAtt("ENVELOPE")); } else if (type == "FULL") { req.fatt.push_back(BincImapParserFetchAtt("FLAGS")); req.fatt.push_back(BincImapParserFetchAtt("INTERNALDATE")); req.fatt.push_back(BincImapParserFetchAtt("RFC822.SIZE")); req.fatt.push_back(BincImapParserFetchAtt("ENVELOPE")); req.fatt.push_back(BincImapParserFetchAtt("BODY")); } else if (type == "FAST") { req.fatt.push_back(BincImapParserFetchAtt("FLAGS")); req.fatt.push_back(BincImapParserFetchAtt("INTERNALDATE")); req.fatt.push_back(BincImapParserFetchAtt("RFC822.SIZE")); } ++f_i; } int mode; if (req.getUidMode()) mode = Mailbox::UID_MODE; else mode = Mailbox::SQNR_MODE; for (Mailbox::iterator i = mailbox->begin(req.bset, Mailbox::SKIP_EXPUNGED | mode); i != mailbox->end(); ++i) { Message &message = *i; bincClient << "* " << i.getSqnr() << " FETCH ("; bool hasprinted = false; f_i = req.fatt.begin(); while (f_i != req.fatt.end()) { BincImapParserFetchAtt fatt = *f_i; string prefix = ""; if (hasprinted) prefix = " "; if (fatt.type == "FLAGS") { // FLAGS hasprinted = true; bincClient << prefix; outputFlags(message); } else if (fatt.type == "UID") { // UID hasprinted = true; bincClient << prefix << "UID " << message.getUID(); } else if (fatt.type == "RFC822.SIZE") { // RFC822.SIZE hasprinted = true; bincClient << prefix << "RFC822.SIZE " << message.getSize(true); } else if (fatt.type == "ENVELOPE") { // ENVELOPE hasprinted = true; bincClient << prefix << "ENVELOPE "; message.printEnvelope(); } else if (fatt.type == "BODYSTRUCTURE") { // BODYSTRUCTURE hasprinted = true; bincClient << prefix << "BODYSTRUCTURE "; message.printBodyStructure(true); } else if (fatt.type == "BODY" && !fatt.hassection) { // BODY with no section hasprinted = true; session.addBody(); bincClient << prefix << "BODY "; message.printBodyStructure(false); } else if (fatt.type == "INTERNALDATE") { // INTERNALDATE hasprinted = true; bincClient << prefix << "INTERNALDATE "; time_t iDate = message.getInternalDate(); struct tm *_tm = gmtime(&iDate); char internal[64]; string iDateStr; if (strftime(internal, sizeof(internal), "%d-%b-%Y %H:%M:%S %z", _tm) != 0) { if (internal[0] == '0') internal[0] = ' '; iDateStr = internal; } else { iDateStr = "NIL"; } bincClient << toImapString(iDateStr); } else if (fatt.type == "BODY" || fatt.type == "BODY.PEEK") { // BODY & BODY.PEEK hasprinted = true; session.addBody(); bincClient << prefix; bool peek = (fatt.type == "BODY.PEEK"); bincClient << fatt.toString(); bool includeheaders = true; bool fullheader = false; bool bodyfetch = false; if (fatt.section != "" || fatt.sectiontext == "" || fatt.sectiontext == "TEXT") { bodyfetch = true; fullheader = true; } if (fatt.sectiontext == "HEADER.FIELDS.NOT") includeheaders = false; if (fatt.sectiontext == "HEADER" || fatt.sectiontext == "HEADER.FIELDS" || fatt.sectiontext == "HEADER.FIELDS.NOT" || fatt.sectiontext == "MIME") { vector v; if (fatt.sectiontext == "MIME") { v.push_back("content-type"); v.push_back("content-transfer-encoding"); v.push_back("content-disposition"); v.push_back("content-description"); } else { v = fatt.headerlist; } string dummy; unsigned int size = fullheader ? message.getHeaderSize(fatt.section, v, true, fatt.offsetstart, fatt.offsetlength, fatt.sectiontext == "MIME") : message.getHeaderSize(fatt.section, fatt.headerlist, includeheaders, fatt.offsetstart, fatt.offsetlength, fatt.sectiontext == "MIME"); bincClient << "{" << size << "}\r\n"; if (fullheader) { message.printHeader(fatt.section, v, true, fatt.offsetstart, fatt.offsetlength, fatt.sectiontext == "MIME"); } else { message.printHeader(fatt.section, fatt.headerlist, includeheaders, fatt.offsetstart, fatt.offsetlength, fatt.sectiontext == "MIME"); } } else { unsigned int size; if ((fatt.sectiontext == "" || fatt.sectiontext == "TEXT") && fatt.section == "") size = message.getDocSize(fatt.offsetstart, fatt.offsetlength, fatt.sectiontext == "TEXT"); else size = message.getBodySize(fatt.section, fatt.offsetstart, fatt.offsetlength); bincClient << "{" << size << "}\r\n"; if ((fatt.sectiontext == "" || fatt.sectiontext == "TEXT") && fatt.section == "") message.printDoc(fatt.offsetstart, fatt.offsetlength, fatt.sectiontext == "TEXT"); else message.printBody(fatt.section, fatt.offsetstart, fatt.offsetlength); } // set the \Seen flag if .PEEK is not used. if (!peek) { if ((message.getStdFlags() & Message::F_SEEN) == 0) message.setStdFlag(Message::F_SEEN); } } else if (fatt.type == "RFC822") { bincClient << prefix; hasprinted = true; session.addBody(); bincClient << fatt.toString(); unsigned int size = message.getDocSize(fatt.offsetstart, fatt.offsetlength); bincClient << " {" << size << "}\r\n"; message.printDoc(fatt.offsetstart, fatt.offsetlength); // set the \Seen flag if ((message.getStdFlags() & Message::F_SEEN) == 0) message.setStdFlag(Message::F_SEEN); } else if (fatt.type == "RFC822.HEADER") { bincClient << prefix; hasprinted = true; bincClient << fatt.toString(); vector v; string dummy; unsigned int size = message.getHeaderSize("", v, true, fatt.offsetstart, fatt.offsetlength); bincClient << " {" << size << "}\r\n"; message.printHeader("", v, true, fatt.offsetstart, fatt.offsetlength); } else if (fatt.type == "RFC822.TEXT") { // RFC822.TEXT bincClient << prefix; hasprinted = true; session.addBody(); bincClient << fatt.toString(); bool bodyfetch = false; bodyfetch = true; unsigned int size; if (fatt.sectiontext == "" && fatt.section == "") size = message.getDocSize(fatt.offsetstart, fatt.offsetlength, true); else size = message.getBodySize(fatt.section, fatt.offsetstart, fatt.offsetlength); bincClient << " {" << size << "}\r\n"; if (fatt.sectiontext == "" && fatt.section == "") message.printDoc(fatt.offsetstart, fatt.offsetlength, true); else message.printBody(fatt.section, fatt.offsetstart, fatt.offsetlength); // set the \Seen flag if ((message.getStdFlags() & Message::F_SEEN) == 0) message.setStdFlag(Message::F_SEEN); } else { // Unrecognized fetch_att, this is stopped by the parser // so we never get here. } f_i++; } // FIXME: how are parse error passed back? bincClient << ")" << std::endl; if (message.hasFlagsChanged()) { updateFlags = true; bincClient << "* " << i.getSqnr() << " FETCH ("; outputFlags(message); bincClient << ")" << std::endl; message.setFlagsUnchanged(); } } if (updateFlags) mailbox->updateFlags(); pendingUpdates(mailbox, PendingUpdates::FLAGS | PendingUpdates::EXISTS | PendingUpdates::EXPUNGE | PendingUpdates::RECENT, true); return Operator::ProcessResult::OK; } Parser::ParseResult FetchOperator::parse(Request &c_in, Parser &p) { Session &session = Session::getInstance(); Parser::ParseResult res; if ((res = p.expectSPACE()) != ACCEPT) { session.setLastError("Expected SPACE after FETCH"); return res; } if ((res = p.expectSet(c_in.getSet())) != ACCEPT) { session.setLastError("Expected sequence set after FETCH SPACE"); return res; } if ((res = p.expectSPACE()) != ACCEPT) { session.setLastError("Expected SPACE after FETCH SPACE set"); return res; } BincImapParserFetchAtt f; if ((res = p.expectThisString("ALL")) == ACCEPT) { f.type = "ALL"; c_in.fatt.push_back(f); } else if ((res = p.expectThisString("FULL")) == ACCEPT) { f.type = "FULL"; c_in.fatt.push_back(f); } else if ((res = p.expectThisString("FAST")) == ACCEPT) { f.type = "FAST"; c_in.fatt.push_back(f); } else if ((res = expectFetchAtt(f, p)) == ACCEPT) { c_in.fatt.push_back(f); } else if ((res = p.expectThisString("(")) == ACCEPT) { while (1) { BincImapParserFetchAtt ftmp; if ((res = expectFetchAtt(ftmp, p)) != ACCEPT) { session.setLastError("Expected fetch_att"); return res; } c_in.fatt.push_back(ftmp); if ((res = p.expectSPACE()) == REJECT) break; else if (res == ERROR) return ERROR; } if ((res = p.expectThisString(")")) != ACCEPT) { session.setLastError("Expected )"); return res; } } else { session.setLastError("Expected ALL, FULL, FAST, fetch_att or ("); return res; } if ((res = p.expectCRLF()) != ACCEPT) { session.setLastError("Expected CRLF"); return res; } c_in.setName("FETCH"); return ACCEPT; } Parser::ParseResult FetchOperator::expectSectionText(BincImapParserFetchAtt &f_in, Parser &p) const { Session &session = Session::getInstance(); Parser::ParseResult res; if ((res = p.expectThisString("HEADER")) == ACCEPT) { f_in.sectiontext = "HEADER"; if ((res = p.expectThisString(".FIELDS")) == ACCEPT) { f_in.sectiontext += ".FIELDS"; if ((res = p.expectThisString(".NOT")) == ACCEPT) f_in.sectiontext += ".NOT"; if ((res = p.expectSPACE()) != ACCEPT) { session.setLastError("expected SPACE"); return res; } if ((res = expectHeaderList(f_in, p)) != ACCEPT) { session.setLastError("Expected header_list"); return res; } } } else if ((res = p.expectThisString("TEXT")) == ACCEPT) { f_in.sectiontext = "TEXT"; } else { return Parser::ParseResult::REJECT; } return ACCEPT; } Parser::ParseResult FetchOperator::expectSection(BincImapParserFetchAtt &f_in, Parser &p) const { Session &session = Session::getInstance(); Parser::ParseResult res; if ((res = p.expectThisString("[")) != ACCEPT) return REJECT; if ((res = expectSectionText(f_in, p)) != ACCEPT) { unsigned int n; if ((res = p.expectNZNumber(n)) == ACCEPT) { BincStream nstr; nstr << n; f_in.section = nstr.str(); bool gotadotalready = false; while (1) { if ((res = p.expectThisString(".")) != ACCEPT) break; if ((res = p.expectNZNumber(n)) != ACCEPT) { gotadotalready = true; break; } f_in.section += "."; BincStream nstr; nstr << n; f_in.section += nstr.str(); } if (gotadotalready || (res = p.expectThisString(".")) == ACCEPT) { if ((res = p.expectThisString("MIME")) == ACCEPT) { f_in.sectiontext = "MIME"; } else if ((res = expectSectionText(f_in, p)) != ACCEPT) { session.setLastError("Expected MIME or section_text"); return res; } } } } if ((res = p.expectThisString("]")) != ACCEPT) { session.setLastError("Expected ]"); return res; } return ACCEPT; } Parser::ParseResult FetchOperator::expectHeaderList(BincImapParserFetchAtt &f_in, Parser &p) const { Session &session = Session::getInstance(); Parser::ParseResult res; if ((res = p.expectThisString("(")) != ACCEPT) return REJECT; string header_fld_name; while (1) { if ((res = p.expectAstring(header_fld_name)) != ACCEPT) { session.setLastError("Expected header_fld_name"); return res; } f_in.headerlist.push_back(header_fld_name); if ((res = p.expectSPACE()) == ACCEPT) continue; else break; } if ((res = p.expectThisString(")")) != ACCEPT) { session.setLastError("Expected )"); return res; } return ACCEPT; } Parser::ParseResult FetchOperator::expectOffset(BincImapParserFetchAtt &f_in, Parser &p) const { Session &session = Session::getInstance(); Parser::ParseResult res; if ((res = p.expectThisString("<")) != ACCEPT) return REJECT; unsigned int i; if ((res = p.expectNumber(i)) != ACCEPT) { session.setLastError("Expected number"); return res; } if ((res = p.expectThisString(".")) != ACCEPT) { session.setLastError("Expected ."); return res; } unsigned int j; if ((res = p.expectNZNumber(j)) != ACCEPT) { session.setLastError("expected nz_number"); return res; } if ((res = p.expectThisString(">")) != ACCEPT) { session.setLastError("Expected >"); return res; } f_in.offsetstart = i; f_in.offsetlength = j; return ACCEPT; } Parser::ParseResult FetchOperator::expectFetchAtt(BincImapParserFetchAtt &f_in, Parser &p) const { Parser::ParseResult res; Session &session = Session::getInstance(); if ((res = p.expectThisString("ENVELOPE")) == ACCEPT) { f_in.type = "ENVELOPE"; } else if ((res = p.expectThisString("FLAGS")) == ACCEPT) { f_in.type = "FLAGS"; } else if ((res = p.expectThisString("INTERNALDATE")) == ACCEPT) { f_in.type = "INTERNALDATE"; } else if ((res = p.expectThisString("UID")) == ACCEPT) { f_in.type = "UID"; } else if ((res = p.expectThisString("RFC822")) == ACCEPT) { f_in.type = "RFC822"; if ((res = p.expectThisString(".HEADER")) == ACCEPT) { f_in.type += ".HEADER"; } else if ((res = p.expectThisString(".SIZE")) == ACCEPT) { f_in.type += ".SIZE"; } else if ((res = p.expectThisString(".TEXT")) == ACCEPT) { f_in.type += ".TEXT"; } else if ((res = p.expectThisString(".")) == ACCEPT) { session.setLastError("Expected RFC822, RFC822.HEADER," " RFC822.SIZE or RFC822.TEXT"); return ERROR; } } else if ((res = p.expectThisString("BODY")) == ACCEPT) { f_in.type = "BODY"; if ((res = p.expectThisString("STRUCTURE")) == ACCEPT) f_in.type += "STRUCTURE"; else if ((res = p.expectThisString(".PEEK")) == ACCEPT) f_in.type += ".PEEK"; if ((res = expectSection(f_in, p)) != ACCEPT) { f_in.hassection = false; } else { f_in.hassection = true; if ((res = expectOffset(f_in, p)) == ERROR) return ERROR; } } else { return REJECT; } return ACCEPT; }