diff options
Diffstat (limited to 'src/messaging/imap/IMAPMessage.cpp')
-rw-r--r-- | src/messaging/imap/IMAPMessage.cpp | 843 |
1 files changed, 843 insertions, 0 deletions
diff --git a/src/messaging/imap/IMAPMessage.cpp b/src/messaging/imap/IMAPMessage.cpp new file mode 100644 index 00000000..629668c5 --- /dev/null +++ b/src/messaging/imap/IMAPMessage.cpp @@ -0,0 +1,843 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// 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 "vmime/messaging/imap/IMAPParser.hpp" +#include "vmime/messaging/imap/IMAPMessage.hpp" +#include "vmime/messaging/imap/IMAPFolder.hpp" +#include "vmime/messaging/imap/IMAPStore.hpp" +#include "vmime/messaging/imap/IMAPConnection.hpp" +#include "vmime/messaging/imap/IMAPUtils.hpp" + +#include <sstream> +#include <iterator> + + +namespace vmime { +namespace messaging { +namespace imap { + + +// +// IMAPpart +// + +class IMAPstructure; + +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: + + ~IMAPpart(); + + 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 + { + if (m_header == NULL) + throw exceptions::unfetched_object(); + else + return (*m_header); + } + + + 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()); + } + + + header& getOrCreateHeader() + { + if (m_header != NULL) + return (*m_header); + else + return (*(m_header = new header())); + } + +private: + + IMAPstructure* m_structure; + IMAPpart* m_parent; + header* 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)); + } + } + + ~IMAPstructure() + { + free_container(m_parts); + } + + + 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; +} + + +IMAPpart::~IMAPpart() +{ + delete (m_structure); + delete (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()); +} + + + +#ifndef VMIME_BUILDING_DOC + +// +// 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; +}; + +#endif // VMIME_BUILDING_DOC + + + +// +// 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 <header*>(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; + text::decodeAndUnfold(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) + { + header 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; +} + + +header& IMAPMessage::getOrCreateHeader() +{ + if (m_header != NULL) + return (*m_header); + else + return (*(m_header = new header())); +} + + +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; + text::decodeAndUnfold(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); + } + } +} + + +} // imap +} // messaging +} // vmime |