diff options
Diffstat (limited to 'vmime-master/src/vmime/net/imap/IMAPUtils.cpp')
-rw-r--r-- | vmime-master/src/vmime/net/imap/IMAPUtils.cpp | 854 |
1 files changed, 854 insertions, 0 deletions
diff --git a/vmime-master/src/vmime/net/imap/IMAPUtils.cpp b/vmime-master/src/vmime/net/imap/IMAPUtils.cpp new file mode 100644 index 0000000..59b1e18 --- /dev/null +++ b/vmime-master/src/vmime/net/imap/IMAPUtils.cpp @@ -0,0 +1,854 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002 Vincent Richard <vincent@vmime.org> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include "vmime/net/imap/IMAPUtils.hpp" +#include "vmime/net/imap/IMAPStore.hpp" + +#include "vmime/net/message.hpp" +#include "vmime/net/folder.hpp" + +#include <sstream> +#include <iterator> +#include <algorithm> + + +namespace vmime { +namespace net { +namespace imap { + + +// static +const string IMAPUtils::quoteString(const string& text) { + + // + // ATOM_CHAR ::= <any CHAR except atom_specials> + // + // atom_specials ::= "(" / ")" / "{" / SPACE / CTL / + // list_wildcards / quoted_specials + // + // list_wildcards ::= "%" / "*" + // + // quoted_specials ::= <"> / "\" + // + // CHAR ::= <any 7-bit US-ASCII character except NUL, + // 0x01 - 0x7f> + // + // CTL ::= <any ASCII control character and DEL, + // 0x00 - 0x1f, 0x7f> + // + + bool needQuoting = text.empty(); + + for (string::const_iterator it = text.begin() ; + !needQuoting && it != text.end() ; ++it) { + + const unsigned char c = *it; + + switch (c) { + + case '(': + case ')': + case '{': + case 0x20: // SPACE + case '%': + case '*': + case '"': + case '\\': + + needQuoting = true; + break; + + default: + + if (c <= 0x1f || c >= 0x7f) { + needQuoting = true; + } + } + } + + if (needQuoting) { + + string quoted; + quoted.reserve((text.length() * 3) / 2 + 2); + + quoted += '"'; + + for (string::const_iterator it = text.begin() ; it != text.end() ; ++it) { + + const unsigned char c = *it; + + if (c == '\\' || c == '"') { + quoted += '\\'; + } + + quoted += c; + } + + quoted += '"'; + + return quoted; + + } else { + + return text; + } +} + + +const string IMAPUtils::pathToString( + const char hierarchySeparator, + const folder::path& path +) { + + string result; + + for (size_t i = 0 ; i < path.getSize() ; ++i) { + + if (i > 0) result += hierarchySeparator; + result += toModifiedUTF7(hierarchySeparator, path[i]); + } + + return (result); +} + + +const folder::path IMAPUtils::stringToPath( + const char hierarchySeparator, + const string& str +) { + + folder::path result; + string::const_iterator begin = str.begin(); + + for (string::const_iterator it = str.begin() ; it != str.end() ; ++it) { + + if (*it == hierarchySeparator) { + result /= fromModifiedUTF7(string(begin, it)); + begin = it + 1; + } + } + + if (begin != str.end()) { + result /= fromModifiedUTF7(string(begin, str.end())); + } + + return result; +} + + +const string IMAPUtils::toModifiedUTF7( + const char hierarchySeparator, + const folder::path::component& text +) { + + // We will replace the hierarchy separator with an equivalent + // UTF-7 sequence, so we compute it here... + const char base64alphabet[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,="; + + const unsigned int hs = static_cast <unsigned int>(static_cast <unsigned char>(hierarchySeparator)); + + string hsUTF7; + hsUTF7.resize(3); + + hsUTF7[0] = base64alphabet[0]; + hsUTF7[1] = base64alphabet[(hs & 0xF0) >> 4]; + hsUTF7[2] = base64alphabet[(hs & 0x0F) << 2]; + + // iconv() is buggy with UTF-8 to UTF-7 conversion, so we do it "by hand". + // This code is largely inspired from "imap/utf7.c", in mutt 1.4. + // Copyright (C) 2000 Edmund Grimley Evans <edmundo@rano.org> + + // WARNING: This may throw "exceptions::charset_conv_error" + const string cvt = text.getConvertedText(charset(charsets::UTF_8)); + + // In the worst case we convert 2 chars to 7 chars. + // For example: "\x10&\x10&..." -> "&ABA-&-&ABA-&-...". + string out; + out.reserve((cvt.length() / 2) * 7 + 6); + + int b = 0, k = 0; + bool base64 = false; + + size_t remaining = cvt.length(); + + for (size_t i = 0, len = cvt.length() ; i < len ; ) { + + const unsigned char c = cvt[i]; + + // Replace hierarchy separator with an equivalent UTF-7 Base64 sequence + if (!base64 && c == hierarchySeparator) { + + out += "&" + hsUTF7 + "-"; + + ++i; + --remaining; + continue; + } + + size_t n = 0; + int ch = 0; + + if (c < 0x80) { + ch = c, n = 0; + } else if (c < 0xc2) { + return ""; + } else if (c < 0xe0) { + ch = c & 0x1f, n = 1; + } else if (c < 0xf0) { + ch = c & 0x0f, n = 2; + } else if (c < 0xf8) { + ch = c & 0x07, n = 3; + } else if (c < 0xfc) { + ch = c & 0x03, n = 4; + } else if (c < 0xfe) { + ch = c & 0x01, n = 5; + } else { + return ""; + } + + if (n > remaining) { + return ""; // error + } + + ++i; + --remaining; + + for (size_t j = 0 ; j < n ; j++) { + + if ((cvt[i + j] & 0xc0) != 0x80) { + return ""; // error + } + + ch = (ch << 6) | (cvt[i + j] & 0x3f); + } + + if (n > 1 && !(ch >> (n * 5 + 1))) { + return ""; // error + } + + i += n; + remaining -= n; + + if (ch < 0x20 || ch >= 0x7f) { + + if (!base64) { + out += '&'; + base64 = true; + b = 0; + k = 10; + } + + if (ch & ~0xffff) { + ch = 0xfffe; + } + + out += base64alphabet[b | ch >> k]; + + k -= 6; + + for ( ; k >= 0 ; k -= 6) { + out += base64alphabet[(ch >> k) & 0x3f]; + } + + b = (ch << (-k)) & 0x3f; + k += 16; + + } else { + + if (base64) { + + if (k > 10) { + out += base64alphabet[b]; + } + + out += '-'; + base64 = false; + } + + out += static_cast <char>(ch); + + if (ch == '&') { + out += '-'; + } + } + } + + if (base64) { + + if (k > 10) { + out += base64alphabet[b]; + } + + out += '-'; + } + + return (out); +} + + +const folder::path::component IMAPUtils::fromModifiedUTF7(const string& text) { + + // Transcode from modified UTF-7 (RFC-2060). + string out; + out.reserve(text.length()); + + bool inB64sequence = false; + bool plusOutput = false; + unsigned char prev = 0; + + for (string::const_iterator it = text.begin() ; it != text.end() ; ++it) { + + const unsigned char c = *it; + + switch (c) { + + // Start of Base64 sequence + case '&': { + + if (!inB64sequence) { + inB64sequence = true; + plusOutput = false; + } else { + out += '&'; + } + + break; + } + // End of Base64 sequence (or "&-" --> "&") + case '-': { + + if (inB64sequence && prev == '&') { // special case "&-" --> "&" + out += '&'; + } else { + out += '-'; + } + + inB64sequence = false; + break; + } + // ',' is used instead of '/' in modified Base64 + case ',': { + + if (inB64sequence && !plusOutput) { + out += '+'; + plusOutput = true; + } + + out += (inB64sequence ? '/' : ','); + break; + } + default: { + + if (inB64sequence && !plusOutput) { + out += '+'; + plusOutput = true; + } + + out += c; + break; + } + } + + prev = c; + } + + // Store it as UTF-8 by default + string cvt; + charset::convert(out, cvt, charset(charsets::UTF_7), charset(charsets::UTF_8)); + + return folder::path::component(cvt, charset(charsets::UTF_8)); +} + + +// static +void IMAPUtils::mailboxFlagsToFolderAttributes( + const shared_ptr <const IMAPConnection>& cnt, + const folder::path &path, + const IMAPParser::mailbox_flag_list& list, + folderAttributes& attribs +) { + + int specialUse = folderAttributes::SPECIALUSE_NONE; + int type = folderAttributes::TYPE_CONTAINS_MESSAGES | folderAttributes::TYPE_CONTAINS_FOLDERS; + int flags = 0; + + // If CHILDREN extension (RFC-3348) is not supported, assume folder has children + // as we have no hint about it + if (!cnt->hasCapability("CHILDREN")) { + flags |= folderAttributes::FLAG_HAS_CHILDREN; + } + + for (auto it = list.flags.begin() ; it != list.flags.end() ; ++it) { + + switch ((*it)->type) { + + case IMAPParser::mailbox_flag::NOSELECT: + + type &= ~folderAttributes::TYPE_CONTAINS_MESSAGES; + flags |= folderAttributes::FLAG_NO_OPEN; + break; + + case IMAPParser::mailbox_flag::NOINFERIORS: + case IMAPParser::mailbox_flag::HASNOCHILDREN: + + flags &= ~folderAttributes::FLAG_HAS_CHILDREN; + break; + + case IMAPParser::mailbox_flag::HASCHILDREN: + + flags |= folderAttributes::FLAG_HAS_CHILDREN; + break; + + case IMAPParser::mailbox_flag::SPECIALUSE_ALL: + + specialUse = folderAttributes::SPECIALUSE_ALL; + break; + + case IMAPParser::mailbox_flag::SPECIALUSE_ARCHIVE: + + specialUse = folderAttributes::SPECIALUSE_ARCHIVE; + break; + + case IMAPParser::mailbox_flag::SPECIALUSE_DRAFTS: + + specialUse = folderAttributes::SPECIALUSE_DRAFTS; + break; + + case IMAPParser::mailbox_flag::SPECIALUSE_FLAGGED: + + specialUse = folderAttributes::SPECIALUSE_FLAGGED; + break; + + case IMAPParser::mailbox_flag::SPECIALUSE_JUNK: + + specialUse = folderAttributes::SPECIALUSE_JUNK; + break; + + case IMAPParser::mailbox_flag::SPECIALUSE_SENT: + + specialUse = folderAttributes::SPECIALUSE_SENT; + break; + + case IMAPParser::mailbox_flag::SPECIALUSE_TRASH: + + specialUse = folderAttributes::SPECIALUSE_TRASH; + break; + + case IMAPParser::mailbox_flag::SPECIALUSE_IMPORTANT: + + specialUse = folderAttributes::SPECIALUSE_IMPORTANT; + break; + + default: + + break; + } + } + + if (path.getSize() == 1 && path.getLastComponent().getBuffer() == "INBOX") { + specialUse = folderAttributes::SPECIALUSE_INBOX; + } + + attribs.setSpecialUse(specialUse); + attribs.setType(type); + attribs.setFlags(flags); +} + + +int IMAPUtils::messageFlagsFromFlags(const IMAPParser::flag_list& list) { + + int flags = 0; + + for (auto &flag : list.flags) { + + switch (flag->type) { + + case IMAPParser::flag::ANSWERED: + + flags |= message::FLAG_REPLIED; + break; + + case IMAPParser::flag::FLAGGED: + + flags |= message::FLAG_MARKED; + break; + + case IMAPParser::flag::DELETED: + + flags |= message::FLAG_DELETED; + break; + + case IMAPParser::flag::SEEN: + + flags |= message::FLAG_SEEN; + break; + + case IMAPParser::flag::DRAFT: + + flags |= message::FLAG_DRAFT; + break; + + default: + //case IMAPParser::flag::UNKNOWN: + + break; + } + } + + return flags; +} + + +// static +const std::vector <string> IMAPUtils::messageFlagList(const int flags) { + + std::vector <string> flagList; + + if (flags == -1) { + return flagList; // default flags + } + + if (flags & message::FLAG_REPLIED) flagList.push_back("\\Answered"); + if (flags & message::FLAG_MARKED) flagList.push_back("\\Flagged"); + if (flags & message::FLAG_DELETED) flagList.push_back("\\Deleted"); + if (flags & message::FLAG_SEEN) flagList.push_back("\\Seen"); + if (flags & message::FLAG_DRAFT) flagList.push_back("\\Draft"); + + return flagList; +} + + +// static +const string IMAPUtils::dateTime(const vmime::datetime& date) { + + std::ostringstream res; + res.imbue(std::locale::classic()); + + // date_time ::= <"> date_day_fixed "-" date_month "-" date_year + // SPACE time SPACE zone <"> + // + // time ::= 2digit ":" 2digit ":" 2digit + // ;; Hours minutes seconds + // zone ::= ("+" / "-") 4digit + // ;; Signed four-digit value of hhmm representing + // ;; hours and minutes west of Greenwich + res << '"'; + + // Date + if (date.getDay() < 10) res << ' '; + res << date.getDay(); + + res << '-'; + + static const char* monthNames[12] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + + res << monthNames[std::min(std::max(date.getMonth() - 1, 0), 11)]; + + res << '-'; + + if (date.getYear() < 10) res << '0'; + if (date.getYear() < 100) res << '0'; + if (date.getYear() < 1000) res << '0'; + res << date.getYear(); + + res << ' '; + + // Time + if (date.getHour() < 10) res << '0'; + res << date.getHour() << ':'; + + if (date.getMinute() < 10) res << '0'; + res << date.getMinute() << ':'; + + if (date.getSecond() < 10) res << '0'; + res << date.getSecond(); + + res << ' '; + + // Zone + const int zs = (date.getZone() < 0 ? -1 : 1); + const int zh = (date.getZone() * zs) / 60; + const int zm = (date.getZone() * zs) % 60; + + res << (zs < 0 ? '-' : '+'); + + if (zh < 10) res << '0'; + res << zh; + + if (zm < 10) res << '0'; + res << zm; + + res << '"'; + + return res.str(); +} + + +// static +shared_ptr <IMAPCommand> IMAPUtils::buildFetchCommand( + const shared_ptr <IMAPConnection>& cnt, + const messageSet& msgs, + const fetchAttributes& options +) { + + // Example: + // C: A654 FETCH 2:4 (FLAGS BODY[HEADER.FIELDS (DATE FROM)]) + // S: * 2 FETCH .... + // S: * 3 FETCH .... + // S: * 4 FETCH .... + // S: A654 OK FETCH completed + + std::vector <string> items; + + if (options.has(fetchAttributes::SIZE)) { + items.push_back("RFC822.SIZE"); + } + + if (options.has(fetchAttributes::FLAGS)) { + items.push_back("FLAGS"); + } + + if (options.has(fetchAttributes::STRUCTURE)) { + items.push_back("BODYSTRUCTURE"); + } + + if (options.has(fetchAttributes::UID)) { + + items.push_back("UID"); + + // Also fetch MODSEQ if CONDSTORE is supported + if (cnt && cnt->hasCapability("CONDSTORE") && !cnt->isMODSEQDisabled()) { + items.push_back("MODSEQ"); + } + } + + if (options.has(fetchAttributes::FULL_HEADER)) { + + items.push_back("RFC822.HEADER"); + + } else { + + if (options.has(fetchAttributes::ENVELOPE)) { + items.push_back("ENVELOPE"); + } + + std::vector <string> headerFields; + + if (options.has(fetchAttributes::CONTENT_INFO)) { + headerFields.push_back("CONTENT_TYPE"); + } + + if (options.has(fetchAttributes::IMPORTANCE)) { + headerFields.push_back("IMPORTANCE"); + headerFields.push_back("X-PRIORITY"); + } + + // Also add custom header fields to fetch, if any + const std::vector <string> customHeaderFields = options.getHeaderFields(); + std::copy(customHeaderFields.begin(), customHeaderFields.end(), std::back_inserter(headerFields)); + + if (!headerFields.empty()) { + + string list; + + for (std::vector <string>::iterator it = headerFields.begin() ; + it != headerFields.end() ; ++it) { + + if (it != headerFields.begin()) { + list += " "; + } + + list += *it; + } + + items.push_back("BODY[HEADER.FIELDS (" + list + ")]"); + } + } + + return IMAPCommand::FETCH(msgs, items); +} + + +// static +void IMAPUtils::convertAddressList( + const IMAPParser::address_list& src, + mailboxList& dest +) { + + for (auto& addr : src.addresses) { + + text name; + text::decodeAndUnfold(addr->addr_name->value, &name); + + string email = addr->addr_mailbox->value + + "@" + addr->addr_host->value; + + dest.appendMailbox(make_shared <mailbox>(name, email)); + } +} + + + +class IMAPUIDMessageSetEnumerator : public messageSetEnumerator { + +public: + + IMAPUIDMessageSetEnumerator() + : m_first(true) { + + m_oss.imbue(std::locale::classic()); + } + + void enumerateNumberMessageRange(const vmime::net::numberMessageRange& range) { + + if (!m_first) { + m_oss << ","; + } + + if (range.getFirst() == range.getLast()) { + m_oss << range.getFirst(); + } else if (range.getLast() == size_t(-1)) { + m_oss << range.getFirst() << ":*"; + } else { + m_oss << range.getFirst() << ":" << range.getLast(); + } + + m_first = false; + } + + void enumerateUIDMessageRange(const vmime::net::UIDMessageRange& range) { + + if (!m_first) { + m_oss << ","; + } + + if (range.getFirst() == range.getLast()) { + m_oss << range.getFirst(); + } else if (range.getLast() == size_t(-1)) { + m_oss << range.getFirst() << ":*"; + } else { + m_oss << range.getFirst() << ":" << range.getLast(); + } + + m_first = false; + } + + const std::string str() const { + + return m_oss.str(); + } + +private: + + std::ostringstream m_oss; + bool m_first; +}; + + +class IMAPMessageSetEnumerator : public messageSetEnumerator { + +public: + + void enumerateNumberMessageRange(const vmime::net::numberMessageRange& range) { + + for (size_t i = range.getFirst(), last = range.getLast() ; i <= last ; ++i) { + m_list.push_back(i); + } + } + + void enumerateUIDMessageRange(const vmime::net::UIDMessageRange& /* range */) { + + // Not used + } + + const std::vector <size_t>& list() const { + + return m_list; + } + +public: + + std::vector <size_t> m_list; +}; + + + +// static +const string IMAPUtils::messageSetToSequenceSet(const messageSet& msgs) { + + IMAPUIDMessageSetEnumerator en; + msgs.enumerate(en); + + return en.str(); +} + + +// static +messageSet IMAPUtils::buildMessageSet(const IMAPParser::uid_set& uidSetRef) { + + messageSet set = messageSet::empty(); + + const IMAPParser::uid_set* uidSet = &uidSetRef; + + for ( ; uidSet ; uidSet = uidSet->next_uid_set.get()) { + + if (uidSet->uid_range) { + + set.addRange( + UIDMessageRange( + message::uid(uidSet->uid_range->uniqueid1->value), + message::uid(uidSet->uid_range->uniqueid2->value) + ) + ); + + } else { + + set.addRange( + UIDMessageRange( + message::uid(uidSet->uniqueid->value) + ) + ); + } + } + + return set; +} + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + |