// // VMime library (http://vmime.sourceforge.net) // Copyright (C) 2002-2004 Vincent Richard // // 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. // #include "IMAPUtils.hpp" #include "message.hpp" #include #include #include namespace vmime { namespace messaging { const string IMAPUtils::quoteString(const string& text) { // // ATOM_CHAR ::= // // atom_specials ::= "(" / ")" / "{" / SPACE / CTL / // list_wildcards / quoted_specials // // list_wildcards ::= "%" / "*" // // quoted_specials ::= <"> / "\" // // CHAR ::= // // CTL ::= // 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() ; !needQuoting && 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 (int i = 0 ; i < path.size() ; ++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 = (unsigned int)(unsigned char) hierarchySeparator; string hsUTF7; hsUTF7.resize(3); hsUTF7[0] = base64alphabet[0]; hsUTF7[1] = base64alphabet[(hs & 0xF0) >> 4]; hsUTF7[2] = base64alphabet[(hs & 0x0F) << 2]; // Transcode path component to UTF-7 charset. // WARNING: This may throw "exceptions::charset_conv_error" const string cvt = text.getConvertedText(charset(charsets::UTF_7)); // Transcode to modified UTF-7 (RFC-2060). string out; out.reserve((cvt.length() * 3) / 2); bool inB64sequence = false; for (string::const_iterator it = cvt.begin() ; it != cvt.end() ; ++it) { const unsigned char c = *it; // Replace hierarchy separator with an equivalent UTF-7 Base64 sequence if (!inB64sequence && c == hierarchySeparator) { out += "&" + hsUTF7 + "-"; continue; } switch (c) { // Beginning of Base64 sequence: replace '+' with '&' case '+': { if (!inB64sequence) { inB64sequence = true; out += '&'; } else { out += '+'; } break; } // End of Base64 sequence case '-': { inB64sequence = false; out += '-'; break; } // ',' is used instead of '/' in modified Base64 case '/': { out += inB64sequence ? ',' : '/'; break; } // '&' (0x26) is represented by the two-octet sequence "&-" case '&': { if (!inB64sequence) out += "&-"; else out += '&'; break; } default: { out += c; break; } } } 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; 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; out += '+'; } else { out += '&'; } break; } // End of Base64 sequence (or "&-" --> "&") case '-': { if (inB64sequence && prev == '&') out += '&'; else out += '-'; inB64sequence = false; break; } // ',' is used instead of '/' in modified Base64 case ',': { out += (inB64sequence ? '/' : ','); break; } default: { 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))); } const int IMAPUtils::folderTypeFromFlags(const IMAPParser::mailbox_flag_list* list) { // Get folder type int type = folder::TYPE_CONTAINS_MESSAGES | folder::TYPE_CONTAINS_FOLDERS; const std::vector & flags = list->flags(); for (std::vector ::const_iterator it = flags.begin() ; it != flags.end() ; ++it) { if ((*it)->type() == IMAPParser::mailbox_flag::NOSELECT) type &= ~folder::TYPE_CONTAINS_MESSAGES; } if (type & folder::TYPE_CONTAINS_MESSAGES) type &= ~folder::TYPE_CONTAINS_FOLDERS; return (type); } const int IMAPUtils::folderFlagsFromFlags(const IMAPParser::mailbox_flag_list* list) { // Get folder flags int folderFlags = folder::FLAG_CHILDREN; const std::vector & flags = list->flags(); for (std::vector ::const_iterator it = flags.begin() ; it != flags.end() ; ++it) { if ((*it)->type() == IMAPParser::mailbox_flag::NOSELECT) folderFlags |= folder::FLAG_NO_OPEN; else if ((*it)->type() == IMAPParser::mailbox_flag::NOINFERIORS) folderFlags &= ~folder::FLAG_CHILDREN; } return (folderFlags); } const int IMAPUtils::messageFlagsFromFlags(const IMAPParser::flag_list* list) { const std::vector & flagList = list->flags(); int flags = 0; for (std::vector ::const_iterator it = flagList.begin() ; it != flagList.end() ; ++it) { switch ((*it)->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; default: //case IMAPParser::flag::UNKNOWN: //case IMAPParser::flag::DRAFT: break; } } return (flags); } const string IMAPUtils::messageFlagList(const int flags) { std::vector flagList; 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 (!flagList.empty()) { std::ostringstream res; res << "("; if (flagList.size() >= 2) { std::copy(flagList.begin(), flagList.end() - 1, std::ostream_iterator (res, " ")); } res << *(flagList.end() - 1) << ")"; return (res.str()); } return ""; } // This function builds a "IMAP set" given a list. Try to group consecutive // message numbers to reduce the list. // // Example: // IN = "1,2,3,4,5,7,8,13,15,16,17" // OUT = "1:5,7:8,13,15:*" for a mailbox with a total of 17 messages (max = 17) const string IMAPUtils::listToSet(const std::vector & list, const int max, const bool alreadySorted) { // Sort a copy of the list (if not already sorted) std::vector temp; if (!alreadySorted) { temp.resize(list.size()); std::copy(list.begin(), list.end(), temp.begin()); std::sort(temp.begin(), temp.end()); } const std::vector & theList = (alreadySorted ? list : temp); // Build the set std::ostringstream res; int previous = -1, setBegin = -1; for (std::vector ::const_iterator it = theList.begin() ; it != theList.end() ; ++it) { const int current = *it; if (previous == -1) { res << current; previous = current; setBegin = current; } else { if (current == previous + 1) { previous = current; } else { if (setBegin != previous) { res << ":" << previous << "," << current; previous = current; setBegin = current; } else { if (setBegin != current) // skip duplicates res << "," << current; previous = current; setBegin = current; } } } } if (previous != setBegin) { if (previous == max) res << ":*"; else res << ":" << previous; } return (res.str()); } const string IMAPUtils::dateTime(const vmime::datetime& date) { std::ostringstream res; // 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.day() < 10) res << ' '; res << date.day(); 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.month() - 1, 0), 11)]; res << '-'; if (date.year() < 10) res << '0'; if (date.year() < 100) res << '0'; if (date.year() < 1000) res << '0'; res << date.year(); res << ' '; // Time if (date.hour() < 10) res << '0'; res << date.hour() << ':'; if (date.minute() < 10) res << '0'; res << date.minute() << ':'; if (date.second() < 10) res << '0'; res << date.second(); res << ' '; // Zone const int zs = (date.zone() < 0 ? -1 : 1); const int zh = (date.zone() * zs) / 60; const int zm = (date.zone() * zs) % 60; res << (zs < 0 ? '-' : '+'); if (zh < 10) res << '0'; res << zh; if (zm < 10) res << '0'; res << zm; res << '"'; return (res.str()); } } // messaging } // vmime