/** * @file depot.cc * @brief Implementation of the Depot class. * @author Andreas Aardal Hanssen * @date 2002-2005 */ #include "depot.h" #include "convert.h" #include "iodevice.h" #include "iofactory.h" #include "mailbox.h" #include "status.h" #include #include #include #include #include using namespace Binc; using std::endl; using std::string; DepotFactory::DepotFactory(void) {} DepotFactory::~DepotFactory(void) { for (auto i : depots) delete i; } Depot *DepotFactory::get(const string &name) const { for (const auto i : depots) if (i->getName() == name) return i; return nullptr; } void DepotFactory::assign(Depot *depot) { depots.push_back(depot); } DepotFactory &DepotFactory::getInstance(void) { static DepotFactory depotfactory; return depotfactory; } Depot::Depot() : Depot("") {} Depot::Depot(const std::string &name) : enditerator(nullptr, nullptr) , defaultmailbox(nullptr) , selectedmailbox(nullptr) , name(name) , delimiter('/') {} const string &Depot::getLastError(void) const { return lastError; } void Depot::setLastError(const string &error) const { lastError = error; } void Depot::assign(Mailbox *m) { for (const auto i : backends) if (i == m) break; backends.push_back(m); } Mailbox *Depot::get(const string &s_in) const { for (const auto i : backends) if (i->isMailbox(mailboxToFilename(s_in))) return i; setLastError("No such mailbox " + toImapString(s_in)); return nullptr; } bool Depot::setSelected(Mailbox *m) { for (const auto i : backends) { if (i == m) { selectedmailbox = m; return true; } } setLastError("Attempted to select unregistered Mailbox type in Depot"); return false; } const string &Depot::getName(void) const { return name; } const string &Depot::getPersonalNamespace(void) const { return personalNamespace; } const string &Depot::getOthersNamespace(void) const { return othersNamespace; } const string &Depot::getSharedNamespace(void) const { return sharedNamespace; } void Depot::setDelimiter(char c) { delimiter = c; } char Depot::getDelimiter(void) const { return delimiter; } bool Depot::setDefaultType(const string &name) { for (const auto i : backends) { if (i->getTypeName() == name) { defaultmailbox = i; return true; } } setLastError("attempt to default to unregistered Mailbox type " + name); return false; } Mailbox *Depot::getSelected(void) const { return selectedmailbox; } void Depot::resetSelected(void) { selectedmailbox = nullptr; } Mailbox *Depot::getDefault(void) const { return defaultmailbox; } bool Depot::createMailbox(const string &s_in) const { const string &mailboxname = mailboxToFilename(toCanonMailbox(s_in)); if (mailboxname.empty()) { setLastError("invalid mailbox name"); return false; } Mailbox *mailbox = getDefault(); if (mailbox == nullptr) { setLastError("no default mailbox defined"); return false; } bool result = mailbox->createMailbox(mailboxname, 0777); if (result) { return true; } else { setLastError(mailbox->getLastError()); return false; } } bool Depot::deleteMailbox(const string &s_in) const { const string &mailboxname = mailboxToFilename(toCanonMailbox(s_in)); Mailbox *mailbox = get(s_in); if (mailbox == nullptr) { setLastError(s_in + ": no such mailbox"); return false; } bool result = mailbox->deleteMailbox(mailboxname); if (result) { return true; } else { setLastError(mailbox->getLastError()); return false; } } bool Depot::renameMailbox(const string &s_in, const string &t_in) const { const string &source = mailboxToFilename(s_in); const string &dest = mailboxToFilename(t_in); int nrenamed = 0; const iterator e = end(); for (iterator i = begin("."); i != e; ++i) { string entry = *i; if (entry.substr(0, source.length()) == source) { string sourcename, destname; if (entry.length() == source.length()) { sourcename = source; destname = dest; } else if (entry.length() > source.length() && entry[source.length()] == '.') { sourcename = entry; destname = dest + entry.substr(source.length()); } else { continue; } if (rename(sourcename.c_str(), destname.c_str()) != 0) { bincWarning << "error renaming " << sourcename << " to " << destname << ": " << strerror(errno) << endl; } else { nrenamed++; } Mailbox *mailbox; if ((mailbox = get(filenameToMailbox(sourcename))) != nullptr) mailbox->bumpUidValidity(filenameToMailbox(sourcename)); if ((mailbox = get(filenameToMailbox(destname))) != nullptr) mailbox->bumpUidValidity(filenameToMailbox(destname)); } } if (nrenamed == 0) { setLastError("An error occurred when renaming " + toImapString(s_in) + " to " + toImapString(t_in) + ". Try creating a new mailbox," " then copy over all messages." " Finally, delete the original mailbox"); return false; } else { return true; } } bool Depot::getStatus(const std::string &s_in, Status &dest) const { const string mailbox = toCanonMailbox(s_in); Mailbox *m = get(mailbox); if (m == nullptr) { setLastError("Unrecognized mailbox: " + toImapString(s_in)); return false; } int statusid = m->getStatusID(mailboxToFilename(mailbox)); if (mailboxstatuses.find(mailbox) != mailboxstatuses.end()) { dest = mailboxstatuses[mailbox]; if (dest.getStatusID() == statusid) return true; } if (!m->getStatus(mailboxToFilename(mailbox), dest)) { setLastError(m->getLastError()); return false; } dest.setStatusID(statusid); mailboxstatuses[mailbox] = dest; return true; } std::vector Depot::getSubscriptions(void) const { return subscribed; } void Depot::subscribeTo(const string mailbox) { for (const auto &i : subscribed) if (i == mailbox) return; subscribed.push_back(mailbox); } bool Depot::unsubscribeTo(const string mailbox) { for (auto i = subscribed.begin(); i != subscribed.end(); ++i) { if (*i == mailbox) { subscribed.erase(i); return true; } } return false; } void Depot::loadSubscribes(void) { // drop all existing subscribed folders. subscribed.clear(); // try loading the .subscribed file bool ok = false; FILE *fp = fopen(".subscribed", "r"); std::map addedEntries; if (fp) { int c; string current; while ((c = fgetc(fp)) != EOF) { if (c == '\n') { if (current != "") { if (current == "INBOX") current = "."; if (current.substr(0, 5) == "INBOX") current = current.substr(5); if (addedEntries.find(current) == addedEntries.end()) { subscribed.push_back(filenameToMailbox(current)); addedEntries[current] = true; } current = ""; } } else { current += c; } } fclose(fp); ok = true; } if (!ok) { subscribed.push_back("INBOX"); saveSubscribes(); } } bool Depot::saveSubscribes(void) const { // create a safe file name string ftemplate = ".subscribed-tmp-XXXXXX"; int fd = mkstemp(ftemplate.data()); if (fd == -1) { bincWarning << "unable to create temporary file \"" << ftemplate << "\"" << endl; return false; } std::map addedEntries; for (const auto &i : subscribed) { if (addedEntries.find(i) == addedEntries.end()) { addedEntries[i] = true; string w = mailboxToFilename(i) + "\n"; if (write(fd, w.c_str(), w.length()) != (ssize_t)w.length()) { bincWarning << "failed to write to " << ftemplate << ": " << strerror(errno) << endl; break; } } } if ((fsync(fd) && (errno != EROFS || errno != EINVAL)) || close(fd)) { bincWarning << "failed to close " << ftemplate << ": " << strerror(errno) << endl; return false; } if (rename(ftemplate.c_str(), ".subscribed") != 0) { bincWarning << "failed to rename " << ftemplate << " to .subscribed: " << strerror(errno) << endl; return false; } return true; } Depot::iterator::iterator(void) { dirp = nullptr; ref = new int; *ref = 1; } Depot::iterator::iterator(DIR *dp, struct dirent *sp) { dirp = dp; direntp = sp; ref = new int; *ref = 1; } Depot::iterator::iterator(const iterator ©) { if (*copy.ref != 0) ++(*copy.ref); ref = copy.ref; dirp = copy.dirp; direntp = copy.direntp; } Depot::iterator::~iterator(void) { deref(); } Depot::iterator &Depot::iterator::operator=(const iterator ©) { if (*copy.ref != 0) ++(*copy.ref); deref(); ref = copy.ref; dirp = copy.dirp; direntp = copy.direntp; return *this; } void Depot::iterator::deref(void) { // decrease existing copy ref if there is one if (*ref != 0 && --(*ref) == 0) { if (dirp) { closedir(dirp); dirp = nullptr; } delete ref; ref = nullptr; } } string Depot::iterator::operator*(void) const { if (direntp == nullptr) return ""; return direntp->d_name; } void Depot::iterator::operator++(void) { direntp = readdir(dirp); } bool Depot::iterator::operator==(Depot::iterator i) const { return direntp == i.direntp; } bool Depot::iterator::operator!=(Depot::iterator i) const { return direntp != i.direntp; } Depot::iterator Depot::begin(const string &path) const { Depot::iterator i; if ((i.dirp = opendir(path.c_str())) == nullptr) { bincWarning << "opendir on " << path << " failed" << endl; setLastError("opendir on " + path + " failed"); return end(); } ++i; return i; } const Depot::iterator &Depot::end(void) const { return enditerator; } MaildirPPDepot::MaildirPPDepot(void) : Depot("Maildir++") { privateNamespace = "INBOX"; privateNamespace += getDelimiter(); } MaildirPPDepot::~MaildirPPDepot(void) {} const string &MaildirPPDepot::getPersonalNamespace(void) const { return privateNamespace; } string MaildirPPDepot::mailboxToFilename(const string &m) const { string prefix = "INBOX"; prefix += delimiter; string mm = m; trim(mm, string(&delimiter, 1)); string tmp = mm; uppercase(tmp); if (tmp != "INBOX" && tmp.substr(0, 6) != prefix) { setLastError("With a Maildir++ depot, you must create all" " mailboxes under INBOX. Try creating" " " + prefix + mm + " ."); return ""; } string twodelim; twodelim += delimiter; twodelim += delimiter; if (mm == "INBOX") { return "."; } else if (mm.length() <= 6) { setLastError("With a Maildir++ depot, you must create all" " mailboxes under INBOX."); return ""; } else if (mm.substr(0, 6) != prefix) { setLastError("With a Maildir++ depot, you must create all" " mailboxes under INBOX."); return ""; } else if (mm.find(twodelim) != string::npos) { setLastError("Invalid character combination " + twodelim + " in mailbox name"); return ""; } else if (mm != "" && delimiter != '.' && mm.substr(1).find('.') != string::npos) { setLastError("Invalid character '.' in mailbox name"); return ""; } else { string tmp = mm.substr(6); for (auto &i : tmp) if (i == delimiter) i = '.'; return "." + tmp; } } string MaildirPPDepot::filenameToMailbox(const string &m) const { if (m == ".") { return "INBOX"; } else if (delimiter != '.' && m.find(delimiter) != string::npos) { return ""; } else if (m != "" && m[0] == '.') { string tmp = m; for (auto &i : tmp) if (i == '.') i = delimiter; return "INBOX" + tmp; } else { return ""; } } IMAPdirDepot::IMAPdirDepot(void) : Depot("IMAPdir") {} IMAPdirDepot::~IMAPdirDepot(void) {} string IMAPdirDepot::mailboxToFilename(const string &m) const { string tmp; string mm = m; trim(mm, string(&delimiter, 1)); string twodelim; twodelim += delimiter; twodelim += delimiter; if (mm.find(twodelim) != string::npos) { setLastError("Invalid character combination " + twodelim + " in mailbox name"); return ""; } for (auto i = mm.cbegin(); i != mm.cend(); ++i) { if (*i == delimiter) tmp += '.'; else if (*i == '\\') tmp += "\\\\"; else if (*i == '.') if (i == mm.cbegin()) tmp += "."; else tmp += "\\."; else tmp += *i; } return tmp; } string IMAPdirDepot::filenameToMailbox(const string &m) const { string tmp; bool escape = false; // hide the magic "." mailbox. if (m == "." || m == "..") return ""; for (auto i = m.cbegin(); i != m.cend(); ++i) { if (*i == '.') { if (i != m.cbegin() && !escape) tmp += delimiter; else if (i == m.cbegin() || escape) tmp += '.'; escape = false; } else if (*i == '\\') { if (!escape) { escape = true; } else { tmp += '\\'; escape = false; } } else if (*i == delimiter) { return ""; } else if (escape) { return ""; } else { tmp += *i; escape = false; } } return tmp; }