/** * @file operator-list.cc * @brief Implementation of the LIST command. */ #include "convert.h" #include "depot.h" #include "iodevice.h" #include "iofactory.h" #include "mailbox.h" #include "operators.h" #include "recursivedescent.h" #include "regmatch.h" #include "session.h" #include #include #include #include #include using namespace Binc; using std::string; namespace { const time_t LIST_CACHE_TIMEOUT = 10; } ListOperator::ListOperator() { cacheTimeout = 0; } ListOperator::~ListOperator() {} const string ListOperator::getName() const { return "LIST"; } Session::State ListOperator::getState() const { return Session::State(Session::AUTHENTICATED | Session::SELECTED); } Operator::ProcessResult ListOperator::process(Depot &depot, Request &command) { Session &session = Session::getInstance(); const char delim = depot.getDelimiter(); // special case: if the mailbox argument is empty, then give a // hard coded reply. string wildcard; if ((wildcard = command.getListMailbox()) == "") { bincClient << "* LIST (\\Noselect) \"" << delim << "\" \"\"" << std::endl; return Operator::ProcessResult::OK; } // remove leading or trailing delimiter in wildcard trim(wildcard, string(&delim, 1)); // convert wildcard to regular expression string regex = toRegex(wildcard, depot.getDelimiter()); string wildcardLower = regex; lowercase(wildcardLower); if (wildcardLower.substr(0, 6) == "^inbox") regex = "^[iI][nN][bB][oO][xX]" + regex.substr(6); // remove leading or trailing delimiter in reference string ref = command.getMailbox(); trim(ref, string(&delim, 1)); wildcardLower = ref; lowercase(wildcardLower); if (wildcardLower.substr(0, 6) == "^inbox") ref = "^[iI][nN][bB][oO][xX]" + ref.substr(6); if (wildcardLower.substr(0, 5) == "inbox" && (wildcardLower.length() == 5 || wildcardLower[5] == delim)) { ref = "INBOX" + ref.substr(5); } // a map from mailbox name to flags std::map mailboxes; if (cacheTimeout == 0 || cacheTimeout < time(nullptr) - LIST_CACHE_TIMEOUT || session.mailboxchanges) { session.mailboxchanges = false; // read through all entries in depository. for (Depot::iterator i = depot.begin("."); i != depot.end(); ++i) { const string path = *i; const string mpath = depot.filenameToMailbox(path); Mailbox *m = nullptr; // skip entries that are not identified as mailboxes if ((m = depot.get(mpath)) == nullptr) continue; // convert file name to mailbox name. skip it if there is no // corresponding mailbox name. string tmp = toCanonMailbox(depot.filenameToMailbox(path)); trim(tmp, string(&delim, 1)); if (tmp == "") { continue; } else { // inherit flags that were already set for this mailbox. int flags = DIR_SELECT; if (m->isMarked(path)) flags |= DIR_MARKED; if (mailboxes.find(tmp) != mailboxes.end()) flags |= mailboxes[tmp]; mailboxes[tmp] = flags; } // now add all superior mailboxes with no flags set if not // added already. string::size_type pos = tmp.rfind(delim); while (pos != string::npos) { tmp = tmp.substr(0, pos); trim(tmp, string(&delim, 1)); if (mailboxes.find(tmp) == mailboxes.end()) mailboxes[tmp] = 0; pos = tmp.rfind(delim); } } // find leaf nodes O(N^2) for (auto i = mailboxes.begin(); i != mailboxes.end(); ++i) { string mailbox = i->first; mailbox += delim; bool leaf = true; for (auto j = mailboxes.begin(); j != mailboxes.end(); ++j) { string::size_type pos = j->first.rfind(delim); if (pos == string::npos) continue; string base = j->first.substr(0, pos + 1); if (mailbox == base) { leaf = false; break; } } if (leaf) { unsigned int flags = i->second; flags |= DIR_LEAF; i->second = flags; } } cache = mailboxes; cacheTimeout = time(nullptr); } else { mailboxes = cache; cacheTimeout = time(nullptr); } // finally, print all mailbox entries with flags. for (auto i = mailboxes.begin(); i != mailboxes.end(); ++i) { if (ref == "" || (ref.length() <= i->first.length() && ref == i->first.substr(0, ref.length()))) { if (regexMatch(i->first.substr(ref.length()), regex) == 0) { bincClient << "* LIST ("; string sep = ""; int flags = i->second; bool noselect = false; if (!(flags & DIR_SELECT)) { bincClient << sep << "\\Noselect"; sep = " "; noselect = true; } if (!noselect) { if (flags & DIR_MARKED) bincClient << sep << "\\Marked"; else bincClient << sep << "\\Unmarked"; sep = " "; } if (flags & DIR_LEAF) bincClient << sep << "\\HasNoChildren"; else bincClient << sep << "\\HasChildren"; sep = " "; if (flags & DIR_NOINFERIORS) bincClient << sep << "\\Noinferiors"; bincClient << ") \"" << depot.getDelimiter() << "\" " << toImapString(i->first) << std::endl; } } } return Operator::ProcessResult::OK; } Parser::ParseResult ListOperator::parse(Request &c_in, Parser &p) { constexpr auto ACCEPT = Parser::ParseResult::ACCEPT; Session &session = Session::getInstance(); if (c_in.getUidMode()) return Parser::ParseResult::REJECT; Parser::ParseResult res; if ((res = p.expectSPACE()) != ACCEPT) { session.setLastError("Expected SPACE after LIST"); return res; } string mailbox; if ((res = p.expectMailbox(mailbox)) != ACCEPT) { session.setLastError("Expected mailbox after LIST SPACE"); return res; } c_in.setMailbox(mailbox); if ((res = p.expectSPACE()) != ACCEPT) { session.setLastError("Expected SPACE after LIST SPACE mailbox"); return res; } string listmailbox; if ((res = p.expectListMailbox(listmailbox)) != ACCEPT) { session.setLastError("Expected list_mailbox after LIST SPACE" " mailbox SPACE"); return res; } if ((res = p.expectCRLF()) != ACCEPT) { session.setLastError("Expected CRLF after LIST SPACE mailbox" " SPACE list_mailbox"); return res; } c_in.setListMailbox(listmailbox); c_in.setName("LIST"); return ACCEPT; }