vmime/src/messaging/IMAPUtils.cpp
2004-10-05 10:28:21 +00:00

554 lines
11 KiB
C++

//
// VMime library (http://vmime.sourceforge.net)
// Copyright (C) 2002-2004 Vincent Richard <vincent@vincent-richard.net>
//
// 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 <sstream>
#include <iterator>
#include <algorithm>
namespace vmime {
namespace messaging {
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() ;
!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 <IMAPParser::mailbox_flag*>& flags = list->flags();
for (std::vector <IMAPParser::mailbox_flag*>::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 <IMAPParser::mailbox_flag*>& flags = list->flags();
for (std::vector <IMAPParser::mailbox_flag*>::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 <IMAPParser::flag*>& flagList = list->flags();
int flags = 0;
for (std::vector <IMAPParser::flag*>::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 <string> 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 <string>(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 <int>& list, const int max,
const bool alreadySorted)
{
// Sort a copy of the list (if not already sorted)
std::vector <int> temp;
if (!alreadySorted)
{
temp.resize(list.size());
std::copy(list.begin(), list.end(), temp.begin());
std::sort(temp.begin(), temp.end());
}
const std::vector <int>& theList = (alreadySorted ? list : temp);
// Build the set
std::ostringstream res;
int previous = -1, setBegin = -1;
for (std::vector <int>::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