aboutsummaryrefslogtreecommitdiff
path: root/vmime-master/src/vmime/net/imap/IMAPUtils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'vmime-master/src/vmime/net/imap/IMAPUtils.cpp')
-rw-r--r--vmime-master/src/vmime/net/imap/IMAPUtils.cpp854
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
+