/** * @file maildirmessage.cc * @brief Implementation of the MaildirMessage class. * @author Andreas Aardal Hanssen * @date Copyright 2002-2005 */ #include "maildirmessage.h" #include "convert.h" #include "iodevice.h" #include "iofactory.h" #include "maildir.h" #include "mime-inputsource.h" #include "mime-utils.h" #include "mime.h" #include #include #include #include #include #include #include #include using namespace Binc; using std::string; using std::vector; string Message::lastError; string MaildirMessage::storage; namespace { void printOneHeader(IODevice &io, const MimePart *message, const string &s_in, bool removecomments = true) { string tmp = ""; HeaderItem hitem; if (message->h.getFirstHeader(s_in, hitem)) { tmp = hitem.getValue(); io << toImapString(unfold(tmp, removecomments)); } else { io << "NIL"; } } void printOneAddressList(IODevice &io, const MimePart *message, const string &s_in, bool removecomments = true) { string tmp = ""; HeaderItem hitem; if (message->h.getFirstHeader(s_in, hitem)) { tmp = hitem.getValue(); vector addr; splitAddr(unfold(tmp, removecomments), addr); if (addr.size() != 0) { io << "("; for (const auto &i : addr) io << Address(i).toParenList(); io << ")"; } else { io << "NIL"; } } else { io << "NIL"; } } void envelope(IODevice &io, const MimePart *message) { HeaderItem hitem; io << "("; printOneHeader(io, message, "date"); io << " "; printOneHeader(io, message, "subject", false); io << " "; printOneAddressList(io, message, "from", false); io << " "; printOneAddressList(io, message, message->h.getFirstHeader("sender", hitem) ? "sender" : "from", false); io << " "; printOneAddressList(io, message, message->h.getFirstHeader("reply-to", hitem) ? "reply-to" : "from", false); io << " "; printOneAddressList(io, message, "to", false); io << " "; printOneAddressList(io, message, "cc", false); io << " "; printOneAddressList(io, message, "bcc", false); io << " "; printOneHeader(io, message, "in-reply-to"); io << " "; printOneHeader(io, message, "message-id"); io << ")"; } void bodyStructure(IODevice &io, const MimePart *message, bool extended) { HeaderItem hitem; if (message->isMultipart() && message->members.size() > 0) { io << "("; for (const auto &i : message->members) bodyStructure(io, &i, extended); io << " "; io << toImapString(message->getSubType()); if (extended) { io << " "; vector parameters; vector headers; string tmp; string type, subtype; tmp = ""; if (message->h.getFirstHeader("content-type", hitem)) { tmp = unfold(hitem.getValue()); trim(tmp); vector v; split(tmp, ";", v); for (string element : v) { trim(element); if (element.find('=') != string::npos) { string::size_type pos = element.find('='); string s = element.substr(0, pos); string t = element.substr(pos + 1); trim(s, " \""); trim(t, " \""); parameters.push_back(s); parameters.push_back(t); } } if (parameters.size() != 0) { io << "("; for (auto i = parameters.cbegin(); i != parameters.cend(); ++i) { if (i != parameters.cbegin()) io << " "; io << toImapString(*i); } io << ")"; } else { io << "NIL"; } } else { io << "NIL"; } // CONTENT-DISPOSITION io << " "; tmp = ""; if (message->h.getFirstHeader("content-disposition", hitem)) { tmp = hitem.getValue(); trim(tmp); vector v; split(tmp, ";", v); if (v.size() > 0) { string disp = v[0]; trim(disp); io << "(" << toImapString(disp); io << " "; if (v.size() > 1) { io << "("; vector::const_iterator i = v.begin(); ++i; bool wrote = false; while (i != v.end()) { string s = *i; trim(s); string::size_type pos = s.find('='); string key = s.substr(0, pos); string value = s.substr(pos + 1); trim(key); trim(value); trim(key, " \""); trim(value, " \""); if (!wrote) wrote = true; else io << " "; io << toImapString(key); io << " "; io << toImapString(value); ++i; } io << ")"; } else { io << "NIL"; } io << ")"; } else { io << "NIL"; } } else { io << "NIL"; } // CONTENT-LANGUAGE io << " "; printOneHeader(io, message, "content-language"); } io << ")"; } else { io << "("; vector parameters; vector headers; string tmp; tmp = ""; string type, subtype; tmp = ""; if (message->h.getFirstHeader("content-type", hitem)) { tmp = unfold(hitem.getValue()); vector v; split(tmp, ";", v); if (v.size() > 0) { vector b; split(v[0], "/", b); if (b.size() > 0) type = b[0]; else type = "text"; if (b.size() > 1) subtype = b[1]; else subtype = "plain"; } for (auto i = v.cbegin(); i != v.cend(); ++i) { if (i == v.cbegin()) continue; string element = *i; trim(element); if (element.find('=') != string::npos) { string::size_type pos = element.find('='); string s = element.substr(0, pos); string t = element.substr(pos + 1); trim(s, " \""); trim(t, " \""); parameters.push_back(s); parameters.push_back(t); } } } else { type = "text"; subtype = "plain"; } io << toImapString(type); io << " "; io << toImapString(subtype); io << " "; if (parameters.size() != 0) { io << "("; for (auto i = parameters.cbegin(); i != parameters.cend(); ++i) { if (i != parameters.cbegin()) io << " "; io << toImapString(*i); } io << ")"; } else { io << "NIL"; } // CONTENT-ID io << " "; printOneHeader(io, message, "content-id"); // CONTENT-DESCRIPTION io << " "; printOneHeader(io, message, "content-description"); // CONTENT-TRANSFER-ENCODING io << " "; tmp = ""; if (message->h.getFirstHeader("content-transfer-encoding", hitem)) { tmp = hitem.getValue(); trim(tmp); io << toImapString(tmp); } else { io << "\"7bit\""; } io << " "; // Size of body in octets io << message->getBodyLength(); lowercase(type); if (type == "text") { io << " "; io << message->getNofBodyLines(); } else if (message->isMessageRFC822()) { io << " "; envelope(io, &message->members[0]); io << " "; bodyStructure(io, &message->members[0], extended); io << " "; io << message->getNofBodyLines(); } // Extension data follows if (extended) { // CONTENT-MD5 io << " "; printOneHeader(io, message, "content-md5"); // CONTENT-DISPOSITION io << " "; tmp = ""; if (message->h.getFirstHeader("content-disposition", hitem)) { tmp = hitem.getValue(); trim(tmp); vector v; split(tmp, ";", v); if (v.size() > 0) { string disp = v[0]; trim(disp); io << "(" << toImapString(disp); io << " "; if (v.size() > 1) { io << "("; vector::const_iterator i = v.begin(); ++i; bool wrote = false; while (i != v.end()) { string s = *i; trim(s); string::size_type pos = s.find('='); string key = s.substr(0, pos); string value = s.substr(pos + 1); trim(key); trim(value); trim(key, " \""); trim(value, " \""); if (!wrote) wrote = true; else io << " "; io << toImapString(key); io << " "; io << toImapString(value); ++i; } io << ")"; } else { io << "NIL"; } io << ")"; } else { io << "NIL"; } } else { io << "NIL"; } // CONTENT-LANGUAGE io << " "; printOneHeader(io, message, "content-language"); // CONTENT-LOCATION io << " "; printOneHeader(io, message, "content-location"); } io << ")"; } } } MaildirMessage::MaildirMessage(Maildir &hom) : fd(-1) , doc(nullptr) , internalFlags(None) , stdflags(F_NONE) , uid(0) , size(0) , unique("") , safeName("") , internaldate(0) , home(hom) , customFlags(nullptr) {} MaildirMessage::MaildirMessage(const MaildirMessage ©) : fd(copy.fd) , doc(copy.doc) , internalFlags(copy.internalFlags) , stdflags(copy.stdflags) , uid(copy.uid) , size(copy.size) , unique(copy.unique) , safeName(copy.safeName) , internaldate(copy.internaldate) , home(copy.home) { if (copy.customFlags) { customFlags = new vector; *customFlags = *copy.customFlags; } else { customFlags = 0; } } MaildirMessage::~MaildirMessage(void) { delete customFlags; } bool MaildirMessage::operator<(const MaildirMessage &a) const { return uid < a.uid; } void MaildirMessage::close(void) { if (fd != -1) { if ((internalFlags & WasWrittenTo) && fsync(fd) != 0 && errno != EINVAL && errno != EROFS) { // FIXME: report this error } if (::close(fd) != 0) { // FIXME: report this error } fd = -1; } // The file will not be moved from tmp/ before this function has // finished. So it's safe to assume that safeName is still valid. if (internalFlags & WasWrittenTo) { if (internaldate != 0) { struct utimbuf tim = {internaldate, internaldate}; utime(safeName.c_str(), &tim); } else { time_t t = time(nullptr); struct utimbuf tim = {t, t}; utime(safeName.c_str(), &tim); } internalFlags &= ~WasWrittenTo; } if (doc) { doc->clear(); delete doc; doc = nullptr; } } void MaildirMessage::setExpunged(void) { internalFlags |= Expunged; } void MaildirMessage::setUnExpunged(void) { internalFlags &= ~Expunged; } void MaildirMessage::setFlagsUnchanged(void) { internalFlags &= ~FlagsChanged; } bool MaildirMessage::hasFlagsChanged(void) const { return (internalFlags & FlagsChanged) != 0; } unsigned char MaildirMessage::getStdFlags(void) const { return stdflags; } bool MaildirMessage::isExpunged(void) const { return (internalFlags & Expunged) != 0; } unsigned int MaildirMessage::getUID(void) const { return uid; } unsigned int MaildirMessage::getSize(bool render) const { if (size == 0 && render) { size = getDocSize(); home.mailboxchanged = true; } return size; } const string &MaildirMessage::getUnique(void) const { return unique; } time_t MaildirMessage::getInternalDate(void) const { return internaldate; } void MaildirMessage::setInternalDate(time_t t) { internaldate = t; } void MaildirMessage::setStdFlag(unsigned char f_in) { internalFlags |= FlagsChanged; stdflags |= f_in; } void MaildirMessage::resetStdFlags(void) { internalFlags |= FlagsChanged; stdflags = F_NONE; } void MaildirMessage::setUID(unsigned int i_in) { uid = i_in; } void MaildirMessage::setSize(unsigned int i_in) { size = i_in; } void MaildirMessage::setUnique(const string &s_in) { unique = s_in; } int MaildirMessage::getFile(void) const { if (fd != -1) return fd; const string &id = getUnique(); MaildirIndexItem *item = home.index.find(id); if (item) { string fpath = home.path + "/cur/" + item->fileName; unsigned int oflags = O_RDONLY; #ifdef HAVE_OLARGEFILE oflags |= O_LARGEFILE; #endif while ((fd = open(fpath.c_str(), oflags)) == -1) { if (errno == ENOENT) { struct stat st; if (lstat(fpath.c_str(), &st) != -1) { bincWarning << "dangling symlink: " << fpath << std::endl; return -1; } } else { bincWarning << "unable to open " << fpath << ": " << strerror(errno) << std::endl; return -1; } home.scanFileNames(); if ((item = home.index.find(id)) == nullptr) break; else fpath = home.path + "/cur/" + item->fileName; } MaildirMessageCache &cache = MaildirMessageCache::getInstance(); cache.addStatus(this, MaildirMessageCache::NotParsed); return fd; } return -1; } void MaildirMessage::setFile(int fd) { this->fd = fd; } void MaildirMessage::setSafeName(const string &name) { safeName = name; } const string &MaildirMessage::getSafeName(void) const { return safeName; } string MaildirMessage::getFileName(void) const { MaildirIndexItem *item = home.index.find(getUnique()); if (!item) { home.scanFileNames(); if ((item = home.index.find(getUnique())) == nullptr) return ""; } return item->fileName; } int MaildirMessage::readChunk(string &chunk) { if (fd == -1) { if ((fd = getFile()) == -1) return -1; } char buffer[1024]; ssize_t readBytes = read(fd, buffer, (size_t)sizeof(buffer)); if (readBytes == -1) { setLastError("Error reading from " + getFileName() + ": " + string(strerror(errno))); return -1; } chunk = string(buffer, readBytes); return readBytes; } bool MaildirMessage::appendChunk(const string &chunk) { if (fd == -1) { setLastError("Error writing to " + getSafeName() + ": File is not open"); return false; } internalFlags |= WasWrittenTo; string out; for (string::const_iterator i = chunk.begin(); i != chunk.end(); ++i) { const char c = *i; if (c != '\r') out += c; } ssize_t wroteBytes = 0; for (;;) { wroteBytes = write(fd, out.c_str(), (size_t)out.length()); if (wroteBytes == -1) { if (errno == EINTR) continue; wroteBytes = 0; } break; } if (wroteBytes == (ssize_t)out.length()) return true; setLastError("Error writing to " + getSafeName() + ": " + string(strerror(errno))); return false; } bool MaildirMessage::parseFull(void) const { MaildirMessageCache &cache = MaildirMessageCache::getInstance(); MaildirMessageCache::ParseStatus ps = cache.getStatus(this); if (ps == MaildirMessageCache::AllParsed && doc) return true; int fd = getFile(); if (fd == -1) return false; // FIXME: parse errors if (!doc) doc = new MimeDocument; doc->parseFull(fd); cache.addStatus(this, MaildirMessageCache::AllParsed); return true; } bool MaildirMessage::parseHeaders(void) const { MaildirMessageCache &cache = MaildirMessageCache::getInstance(); MaildirMessageCache::ParseStatus ps = cache.getStatus(this); if ((ps == MaildirMessageCache::AllParsed || ps == MaildirMessageCache::HeaderParsed) && doc) return true; int fd = getFile(); if (fd == -1) return false; // FIXME: parse errors if (!doc) doc = new MimeDocument; doc->parseOnlyHeader(fd); cache.addStatus(this, MaildirMessageCache::HeaderParsed); return true; } bool MaildirMessage::printBodyStructure(bool extended) const { if (!parseFull()) return false; bodyStructure(bincClient, doc, extended); return true; } bool MaildirMessage::printEnvelope(void) const { if (!parseFull()) return false; envelope(bincClient, doc); return true; } bool MaildirMessage::printHeader(const std::string §ion, std::vector headers, bool includeHeaders, unsigned int startOffset, unsigned int length, bool mime) const { bincClient << storage; storage = ""; return true; } unsigned int MaildirMessage::getHeaderSize(const std::string §ion, std::vector headers, bool includeHeaders, unsigned int startOffset, unsigned int length, bool mime) const { if (section == "") { if (!parseHeaders()) return 0; } else if (!parseFull()) { return 0; } const MimePart *part = doc->getPart(section, "", mime ? MimePart::FetchMime : MimePart::FetchHeader); if (!part) { storage = ""; return 0; } int fd = getFile(); if (fd == -1) return 0; storage = ""; part->printHeader(fd, bincClient, headers, includeHeaders, startOffset, length, storage); return storage.size(); } bool MaildirMessage::printBody(const std::string §ion, unsigned int startOffset, unsigned int length) const { if (!parseFull()) return false; const MimePart *part = doc->getPart(section, ""); if (!part) { storage = ""; return false; } int fd = getFile(); if (fd == -1) return false; storage = ""; part->printBody(fd, bincClient, startOffset, length); return true; } unsigned int MaildirMessage::getBodySize(const std::string §ion, unsigned int startOffset, unsigned int length) const { if (!parseFull()) return false; const MimePart *part = doc->getPart(section, ""); if (!part) { storage = ""; return 0; } if (startOffset > part->bodylength) return 0; unsigned int s = part->bodylength - startOffset; return s < length ? s : length; } bool MaildirMessage::printDoc(unsigned int startOffset, unsigned int length, bool onlyText) const { if (!parseFull()) return false; int fd = getFile(); if (fd == -1) return false; if (onlyText) startOffset += doc->bodystartoffsetcrlf; storage = ""; doc->printDoc(fd, bincClient, startOffset, length); return true; } unsigned int MaildirMessage::getDocSize(unsigned int startOffset, unsigned int length, bool onlyText) const { if (!parseFull()) return false; unsigned int s = doc->size; if (onlyText) s -= doc->bodystartoffsetcrlf; if (startOffset > s) return 0; s -= startOffset; return s < length ? s : length; } bool MaildirMessage::headerContains(const std::string &header, const std::string &text) { if (!parseHeaders()) return false; HeaderItem hitem; if (!doc->h.getFirstHeader(header, hitem)) return false; string tmp = hitem.getValue(); uppercase(tmp); string tmp2 = text; uppercase(tmp2); return (tmp.find(tmp2) != string::npos); } bool MaildirMessage::bodyContains(const std::string &text) { if (!parseFull()) return false; // search the body part of the message.. int fd = getFile(); if (fd == -1) return false; MimeInputSource mimeSource(fd); char c; for (unsigned int i = 0; i < doc->getBodyStartOffset(); ++i) if (!mimeSource.getChar(&c)) break; char *ring = new char[text.length()]; int pos = 0; int length = doc->getBodyLength(); const char *textStr = text.c_str(); unsigned int textLength = text.length(); while (mimeSource.getChar(&c) && length--) { ring[pos % textLength] = toupper(c); if (compareStringToQueue(textStr, ring, pos + 1, textLength)) { delete[] ring; return true; } ++pos; } delete[] ring; return false; } bool MaildirMessage::textContains(const std::string &text) { // search the body part of the message.. int fd = getFile(); if (fd == -1) return false; MimeInputSource mimeSource(fd); char c; char *ring = new char[text.length()]; int pos = 0; const char *textStr = text.c_str(); unsigned int textLength = text.length(); while (mimeSource.getChar(&c)) { ring[pos % textLength] = toupper(c); if (compareStringToQueue(textStr, ring, pos + 1, textLength)) { delete[] ring; return true; } ++pos; } delete[] ring; return false; } const std::string &MaildirMessage::getHeader(const std::string &header) { static string NIL = ""; if (!parseHeaders()) return NIL; HeaderItem hitem; if (!doc->h.getFirstHeader(header, hitem)) return NIL; return hitem.getValue(); } MaildirMessageCache::MaildirMessageCache(void) {} MaildirMessageCache::~MaildirMessageCache(void) { clear(); } MaildirMessageCache &MaildirMessageCache::getInstance(void) { static MaildirMessageCache cache; return cache; } void MaildirMessageCache::addStatus(const MaildirMessage *m, ParseStatus s) { if (statuses.find(m) == statuses.end()) { // Insert status. Perhaps remove oldest status. if (statuses.size() > 2) { MaildirMessage *message = const_cast(parsed.front()); removeStatus(message); } parsed.push_back(m); } statuses[m] = s; } MaildirMessageCache::ParseStatus MaildirMessageCache::getStatus(const MaildirMessage *m) const { if (statuses.find(m) == statuses.end()) return NotParsed; return statuses[m]; } void MaildirMessageCache::clear(void) { for (std::deque::iterator i = parsed.begin(); i != parsed.end(); ++i) parsed.clear(); } void MaildirMessageCache::removeStatus(const MaildirMessage *m) { if (statuses.find(m) == statuses.end()) return; statuses.erase(statuses.find(m)); for (std::deque::iterator i = parsed.begin(); i != parsed.end(); ++i) { if (*i == m) { const_cast(*i)->close(); parsed.erase(i); break; } } } void MaildirMessage::setInternalFlag(unsigned char f) { internalFlags |= f; } unsigned char MaildirMessage::getInternalFlags(void) const { return internalFlags; } void MaildirMessage::clearInternalFlag(unsigned char f) { internalFlags &= ~f; } void MaildirMessage::setCustomFlag(const string &flag) { if (!customFlags) { internalFlags |= FlagsChanged | CustomFlagsChanged; customFlags = new vector; } for (const auto &it : *customFlags) if (it == flag) return; internalFlags |= FlagsChanged | CustomFlagsChanged; customFlags->push_back(flag); } void MaildirMessage::removeCustomFlag(const string &flag) { internalFlags |= FlagsChanged | CustomFlagsChanged; if (!customFlags) return; for (vector::iterator it = customFlags->begin(); it != customFlags->end(); ++it) { if (*it == flag) { customFlags->erase(it); return; } } } void MaildirMessage::resetCustomFlags(void) { internalFlags |= FlagsChanged | CustomFlagsChanged; delete customFlags; customFlags = nullptr; } vector MaildirMessage::getCustomFlags(void) const { if (!customFlags) return vector(); return *customFlags; }