vmime/src/messaging/IMAPMessage.cpp
2004-10-21 15:05:47 +00:00

847 lines
19 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 "IMAPParser.hpp"
#include "IMAPMessage.hpp"
#include "IMAPFolder.hpp"
#include "IMAPStore.hpp"
#include "IMAPConnection.hpp"
#include "IMAPUtils.hpp"
#include <sstream>
#include <iterator>
namespace vmime {
namespace messaging {
//
// IMAPheader
//
class IMAPheader : public header
{
public:
IMAPheader()
{
}
void parse(const string& str)
{
header::parse(str);
}
};
//
// IMAPpart
//
class IMAPpart : public part
{
private:
IMAPpart(IMAPpart* parent, const int number, const IMAPParser::body_type_mpart* mpart);
IMAPpart(IMAPpart* parent, const int number, const IMAPParser::body_type_1part* part);
public:
const structure& getStructure() const;
structure& getStructure();
const IMAPpart* getParent() const { return (m_parent); }
const mediaType& getType() const { return (m_mediaType); }
const int getSize() const { return (m_size); }
const int getNumber() const { return (m_number); }
const header& getHeader() const;
static IMAPpart* create(IMAPpart* parent, const int number, const IMAPParser::body* body)
{
if (body->body_type_mpart())
return new IMAPpart(parent, number, body->body_type_mpart());
else
return new IMAPpart(parent, number, body->body_type_1part());
}
IMAPheader& getOrCreateHeader()
{
if (m_header != NULL)
return (*m_header);
else
return (*(m_header = new IMAPheader()));
}
private:
IMAPstructure* m_structure;
IMAPpart* m_parent;
IMAPheader* m_header;
int m_number;
int m_size;
mediaType m_mediaType;
};
//
// IMAPstructure
//
class IMAPstructure : public structure
{
private:
IMAPstructure()
{
}
public:
IMAPstructure(const IMAPParser::body* body)
{
m_parts.push_back(IMAPpart::create(NULL, 1, body));
}
IMAPstructure(IMAPpart* parent, const std::vector <IMAPParser::body*>& list)
{
int number = 1;
for (std::vector <IMAPParser::body*>::const_iterator
it = list.begin() ; it != list.end() ; ++it, ++number)
{
m_parts.push_back(IMAPpart::create(parent, number, *it));
}
}
const part& operator[](const int x) const
{
return (*m_parts[x - 1]);
}
part& operator[](const int x)
{
return (*m_parts[x - 1]);
}
const int getCount() const
{
return (m_parts.size());
}
static IMAPstructure* emptyStructure()
{
return (&m_emptyStructure);
}
private:
static IMAPstructure m_emptyStructure;
std::vector <IMAPpart*> m_parts;
};
IMAPstructure IMAPstructure::m_emptyStructure;
IMAPpart::IMAPpart(IMAPpart* parent, const int number, const IMAPParser::body_type_mpart* mpart)
: m_parent(parent), m_header(NULL), m_number(number), m_size(0)
{
m_mediaType = vmime::mediaType
("multipart", mpart->media_subtype()->value());
m_structure = new IMAPstructure(this, mpart->list());
}
IMAPpart::IMAPpart(IMAPpart* parent, const int number, const IMAPParser::body_type_1part* part)
: m_parent(parent), m_header(NULL), m_number(number), m_size(0)
{
if (part->body_type_text())
{
m_mediaType = vmime::mediaType
("text", part->body_type_text()->
media_text()->media_subtype()->value());
m_size = part->body_type_text()->body_fields()->body_fld_octets()->value();
}
else if (part->body_type_msg())
{
m_mediaType = vmime::mediaType
("message", part->body_type_msg()->
media_message()->media_subtype()->value());
}
else
{
m_mediaType = vmime::mediaType
(part->body_type_basic()->media_basic()->media_type()->value(),
part->body_type_basic()->media_basic()->media_subtype()->value());
m_size = part->body_type_basic()->body_fields()->body_fld_octets()->value();
}
m_structure = NULL;
}
const class header& IMAPpart::getHeader() const
{
if (m_header == NULL)
throw exceptions::unfetched_object();
else
return (*m_header);
}
const class structure& IMAPpart::getStructure() const
{
if (m_structure != NULL)
return (*m_structure);
else
return (*IMAPstructure::emptyStructure());
}
class structure& IMAPpart::getStructure()
{
if (m_structure != NULL)
return (*m_structure);
else
return (*IMAPstructure::emptyStructure());
}
//
// IMAPMessage_literalHandler
//
class IMAPMessage_literalHandler : public IMAPParser::literalHandler
{
public:
IMAPMessage_literalHandler(utility::outputStream& os, progressionListener* progress)
: m_os(os), m_progress(progress)
{
}
target* targetFor(const IMAPParser::component& comp, const int /* data */)
{
if (typeid(comp) == typeid(IMAPParser::msg_att_item))
{
const int type = static_cast
<const IMAPParser::msg_att_item&>(comp).type();
if (type == IMAPParser::msg_att_item::BODY_SECTION ||
type == IMAPParser::msg_att_item::RFC822_TEXT)
{
return new targetStream(m_progress, m_os);
}
}
return (NULL);
}
private:
utility::outputStream& m_os;
progressionListener* m_progress;
};
//
// IMAPMessage
//
IMAPMessage::IMAPMessage(IMAPFolder* folder, const int num)
: m_folder(folder), m_num(num), m_size(-1), m_flags(FLAG_UNDEFINED),
m_expunged(false), m_header(NULL), m_structure(NULL)
{
m_folder->registerMessage(this);
}
IMAPMessage::~IMAPMessage()
{
if (m_folder)
m_folder->unregisterMessage(this);
delete dynamic_cast <IMAPheader*>(m_header);
}
void IMAPMessage::onFolderClosed()
{
m_folder = NULL;
}
const int IMAPMessage::getNumber() const
{
return (m_num);
}
const message::uid IMAPMessage::getUniqueId() const
{
return (m_uid);
}
const int IMAPMessage::getSize() const
{
if (m_size == -1)
throw exceptions::unfetched_object();
return (m_size);
}
const bool IMAPMessage::isExpunged() const
{
return (m_expunged);
}
const int IMAPMessage::getFlags() const
{
if (m_flags == FLAG_UNDEFINED)
throw exceptions::unfetched_object();
return (m_flags);
}
const structure& IMAPMessage::getStructure() const
{
if (m_structure == NULL)
throw exceptions::unfetched_object();
return (*m_structure);
}
structure& IMAPMessage::getStructure()
{
if (m_structure == NULL)
throw exceptions::unfetched_object();
return (*m_structure);
}
const header& IMAPMessage::getHeader() const
{
if (m_header == NULL)
throw exceptions::unfetched_object();
return (*m_header);
}
void IMAPMessage::extract(utility::outputStream& os, progressionListener* progress,
const int start, const int length) const
{
if (!m_folder)
throw exceptions::folder_not_found();
extract(NULL, os, progress, start, length, false);
}
void IMAPMessage::extractPart
(const part& p, utility::outputStream& os, progressionListener* progress,
const int start, const int length) const
{
if (!m_folder)
throw exceptions::folder_not_found();
extract(&p, os, progress, start, length, false);
}
void IMAPMessage::fetchPartHeader(part& p)
{
if (!m_folder)
throw exceptions::folder_not_found();
std::ostringstream oss;
utility::outputStreamAdapter ossAdapter(oss);
extract(&p, ossAdapter, NULL, 0, -1, true);
static_cast <IMAPpart&>(p).getOrCreateHeader().parse(oss.str());
}
void IMAPMessage::extract(const part* p, utility::outputStream& os, progressionListener* progress,
const int start, const int length, const bool headerOnly) const
{
IMAPMessage_literalHandler literalHandler(os, progress);
// Construct section identifier
std::ostringstream section;
if (p != NULL)
{
const IMAPpart* currentPart = static_cast <const IMAPpart*>(p);
std::vector <int> numbers;
numbers.push_back(currentPart->getNumber());
currentPart = currentPart->getParent();
while (currentPart != NULL)
{
numbers.push_back(currentPart->getNumber());
currentPart = currentPart->getParent();
}
numbers.erase(numbers.end() - 1);
for (std::vector <int>::reverse_iterator it = numbers.rbegin() ; it != numbers.rend() ; ++it)
{
if (it != numbers.rbegin()) section << ".";
section << *it;
}
}
// Build the request text
std::ostringstream command;
command << "FETCH " << m_num << " BODY[";
command << section.str();
if (headerOnly) command << ".MIME"; // "MIME" not "HEADER" for parts
command << "]";
if (start != 0 || length != -1)
command << "<" << start << "." << length << ">";
// Send the request
m_folder->m_connection->send(true, command.str(), true);
// Get the response
utility::auto_ptr <IMAPParser::response> resp
(m_folder->m_connection->readResponse(&literalHandler));
if (resp->isBad() || resp->response_done()->response_tagged()->
resp_cond_state()->status() != IMAPParser::resp_cond_state::OK)
{
throw exceptions::command_error("FETCH",
m_folder->m_connection->getParser()->lastLine(), "bad response");
}
if (!headerOnly)
{
// TODO: update the flags (eg. flag "\Seen" may have been set)
}
}
void IMAPMessage::fetch(IMAPFolder* folder, const int options)
{
if (m_folder != folder)
throw exceptions::folder_not_found();
// TODO: optimization: send the request for multiple
// messages at the same time (FETCH x:y)
// 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 & folder::FETCH_SIZE)
items.push_back("RFC822.SIZE");
if (options & folder::FETCH_FLAGS)
items.push_back("FLAGS");
if (options & folder::FETCH_STRUCTURE)
items.push_back("BODYSTRUCTURE");
if (options & folder::FETCH_UID)
items.push_back("UID");
if (options & folder::FETCH_FULL_HEADER)
items.push_back("RFC822.HEADER");
else
{
if (options & folder::FETCH_ENVELOPE)
items.push_back("ENVELOPE");
if (options & folder::FETCH_CONTENT_INFO)
items.push_back("BODY[HEADER.FIELDS (CONTENT-TYPE)]");
}
// Build the request text
std::ostringstream command;
command << "FETCH " << m_num << " (";
for (std::vector <string>::const_iterator it = items.begin() ;
it != items.end() ; ++it)
{
if (it != items.begin()) command << " ";
command << *it;
}
command << ")";
// Send the request
m_folder->m_connection->send(true, command.str(), true);
// Get the response
utility::auto_ptr <IMAPParser::response> resp(m_folder->m_connection->readResponse());
if (resp->isBad() || resp->response_done()->response_tagged()->
resp_cond_state()->status() != IMAPParser::resp_cond_state::OK)
{
throw exceptions::command_error("FETCH",
m_folder->m_connection->getParser()->lastLine(), "bad response");
}
const std::vector <IMAPParser::continue_req_or_response_data*>& respDataList =
resp->continue_req_or_response_data();
for (std::vector <IMAPParser::continue_req_or_response_data*>::const_iterator
it = respDataList.begin() ; it != respDataList.end() ; ++it)
{
if ((*it)->response_data() == NULL)
{
throw exceptions::command_error("FETCH",
m_folder->m_connection->getParser()->lastLine(), "invalid response");
}
const IMAPParser::message_data* messageData =
(*it)->response_data()->message_data();
// We are only interested in responses of type "FETCH"
if (messageData == NULL || messageData->type() != IMAPParser::message_data::FETCH)
continue;
if (static_cast <int>(messageData->number()) != m_num)
continue;
// Process fetch response for this message
processFetchResponse(options, messageData->msg_att());
}
}
void IMAPMessage::processFetchResponse
(const int options, const IMAPParser::msg_att* msgAtt)
{
// Get message attributes
const std::vector <IMAPParser::msg_att_item*> atts =
msgAtt->items();
int flags = 0;
for (std::vector <IMAPParser::msg_att_item*>::const_iterator
it = atts.begin() ; it != atts.end() ; ++it)
{
switch ((*it)->type())
{
case IMAPParser::msg_att_item::FLAGS:
{
flags |= IMAPUtils::messageFlagsFromFlags((*it)->flag_list());
break;
}
case IMAPParser::msg_att_item::UID:
{
std::ostringstream oss;
oss << m_folder->m_uidValidity << ":" << (*it)->unique_id()->value();
m_uid = oss.str();
break;
}
case IMAPParser::msg_att_item::ENVELOPE:
{
if (!(options & folder::FETCH_FULL_HEADER))
{
const IMAPParser::envelope* env = (*it)->envelope();
vmime::header& hdr = getOrCreateHeader();
// Date
hdr.Date().setValue(env->env_date()->value());
// Subject
text subject;
decodeAndUnfoldText(env->env_subject()->value(), subject);
hdr.Subject().setValue(subject);
// From
mailboxList from;
convertAddressList(*(env->env_from()), from);
if (!from.isEmpty())
hdr.From().setValue(*(from.getMailboxAt(0)));
// To
mailboxList to;
convertAddressList(*(env->env_to()), to);
hdr.To().setValue(to);
// Sender
mailboxList sender;
convertAddressList(*(env->env_sender()), sender);
if (!sender.isEmpty())
hdr.Sender().setValue(*(sender.getMailboxAt(0)));
// Reply-to
mailboxList replyTo;
convertAddressList(*(env->env_reply_to()), replyTo);
if (!replyTo.isEmpty())
hdr.ReplyTo().setValue(*(replyTo.getMailboxAt(0)));
// Cc
mailboxList cc;
convertAddressList(*(env->env_cc()), cc);
if (!cc.isEmpty())
hdr.Cc().setValue(cc);
// Bcc
mailboxList bcc;
convertAddressList(*(env->env_bcc()), bcc);
if (!bcc.isEmpty())
hdr.Bcc().setValue(bcc);
}
break;
}
case IMAPParser::msg_att_item::BODY_STRUCTURE:
{
delete (m_structure);
m_structure = new IMAPstructure((*it)->body());
break;
}
case IMAPParser::msg_att_item::RFC822_HEADER:
{
getOrCreateHeader().parse((*it)->nstring()->value());
break;
}
case IMAPParser::msg_att_item::RFC822_SIZE:
{
m_size = (*it)->number()->value();
break;
}
case IMAPParser::msg_att_item::BODY_SECTION:
{
if (!(options & folder::FETCH_FULL_HEADER))
{
if ((*it)->section()->section_text1() &&
(*it)->section()->section_text1()->type()
== IMAPParser::section_text::HEADER_FIELDS)
{
IMAPheader tempHeader;
tempHeader.parse((*it)->nstring()->value());
vmime::header& hdr = getOrCreateHeader();
std::vector <headerField*> fields = tempHeader.getFieldList();
for (std::vector <headerField*>::const_iterator jt = fields.begin() ;
jt != fields.end() ; ++jt)
{
hdr.appendField((*jt)->clone());
}
}
}
break;
}
case IMAPParser::msg_att_item::INTERNALDATE:
case IMAPParser::msg_att_item::RFC822:
case IMAPParser::msg_att_item::RFC822_TEXT:
case IMAPParser::msg_att_item::BODY:
{
break;
}
}
}
if (options & folder::FETCH_FLAGS)
m_flags = flags;
}
IMAPheader& IMAPMessage::getOrCreateHeader()
{
if (m_header != NULL)
return (*m_header);
else
return (*(m_header = new IMAPheader()));
}
void IMAPMessage::convertAddressList
(const IMAPParser::address_list& src, mailboxList& dest)
{
for (std::vector <IMAPParser::address*>::const_iterator
it = src.addresses().begin() ; it != src.addresses().end() ; ++it)
{
const IMAPParser::address& addr = **it;
text name;
decodeAndUnfoldText(addr.addr_name()->value(), name);
string email = addr.addr_mailbox()->value()
+ "@" + addr.addr_host()->value();
dest.appendMailbox(new mailbox(name, email));
}
}
void IMAPMessage::setFlags(const int flags, const int mode)
{
if (!m_folder)
throw exceptions::folder_not_found();
else if (m_folder->m_mode == folder::MODE_READ_ONLY)
throw exceptions::illegal_state("Folder is read-only");
// Build the request text
std::ostringstream command;
command << "STORE " << m_num;
switch (mode)
{
case FLAG_MODE_ADD: command << " +FLAGS"; break;
case FLAG_MODE_REMOVE: command << " -FLAGS"; break;
default:
case FLAG_MODE_SET: command << " FLAGS"; break;
}
if (m_flags == FLAG_UNDEFINED) // Update local flags only if they
command << ".SILENT "; // have been fetched previously
else
command << " ";
std::vector <string> flagList;
if (flags & FLAG_REPLIED) flagList.push_back("\\Answered");
if (flags & FLAG_MARKED) flagList.push_back("\\Flagged");
if (flags & FLAG_DELETED) flagList.push_back("\\Deleted");
if (flags & FLAG_SEEN) flagList.push_back("\\Seen");
if (!flagList.empty())
{
command << "(";
if (flagList.size() >= 2)
{
std::copy(flagList.begin(), flagList.end() - 1,
std::ostream_iterator <string>(command, " "));
}
command << *(flagList.end() - 1) << ")";
// Send the request
m_folder->m_connection->send(true, command.str(), true);
// Get the response
utility::auto_ptr <IMAPParser::response> resp(m_folder->m_connection->readResponse());
if (resp->isBad() || resp->response_done()->response_tagged()->
resp_cond_state()->status() != IMAPParser::resp_cond_state::OK)
{
throw exceptions::command_error("STORE",
m_folder->m_connection->getParser()->lastLine(), "bad response");
}
// Update the local flags for this message
if (m_flags != FLAG_UNDEFINED)
{
const std::vector <IMAPParser::continue_req_or_response_data*>& respDataList =
resp->continue_req_or_response_data();
int newFlags = 0;
for (std::vector <IMAPParser::continue_req_or_response_data*>::const_iterator
it = respDataList.begin() ; it != respDataList.end() ; ++it)
{
if ((*it)->response_data() == NULL)
continue;
const IMAPParser::message_data* messageData =
(*it)->response_data()->message_data();
// We are only interested in responses of type "FETCH"
if (messageData == NULL || messageData->type() != IMAPParser::message_data::FETCH)
continue;
// Get message attributes
const std::vector <IMAPParser::msg_att_item*> atts =
messageData->msg_att()->items();
for (std::vector <IMAPParser::msg_att_item*>::const_iterator
it = atts.begin() ; it != atts.end() ; ++it)
{
if ((*it)->type() == IMAPParser::msg_att_item::FLAGS)
newFlags |= IMAPUtils::messageFlagsFromFlags((*it)->flag_list());
}
}
m_flags = newFlags;
}
// Notify message flags changed
std::vector <int> nums;
nums.push_back(m_num);
events::messageChangedEvent event(m_folder, events::messageChangedEvent::TYPE_FLAGS, nums);
for (std::list <IMAPFolder*>::iterator it = m_folder->m_store->m_folders.begin() ;
it != m_folder->m_store->m_folders.end() ; ++it)
{
if ((*it)->getFullPath() == m_folder->m_path)
(*it)->notifyMessageChanged(event);
}
}
}
} // messaging
} // vmime