// // VMime library (http://www.vmime.org) // Copyright (C) 2002-2005 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 "vmime/messaging/IMAPParser.hpp" #include "vmime/messaging/IMAPMessage.hpp" #include "vmime/messaging/IMAPFolder.hpp" #include "vmime/messaging/IMAPStore.hpp" #include "vmime/messaging/IMAPConnection.hpp" #include "vmime/messaging/IMAPUtils.hpp" #include #include namespace vmime { namespace messaging { // // 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 & list) { int number = 1; for (std::vector ::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 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 (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 (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 (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 (p); std::vector 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 ::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 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 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 ::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 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 & respDataList = resp->continue_req_or_response_data(); for (std::vector ::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 (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 atts = msgAtt->items(); int flags = 0; for (std::vector ::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 fields = tempHeader.getFieldList(); for (std::vector ::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 ::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 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 (command, " ")); } command << *(flagList.end() - 1) << ")"; // Send the request m_folder->m_connection->send(true, command.str(), true); // Get the response utility::auto_ptr 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 & respDataList = resp->continue_req_or_response_data(); int newFlags = 0; for (std::vector ::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 atts = messageData->msg_att()->items(); for (std::vector ::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 nums; nums.push_back(m_num); events::messageChangedEvent event(m_folder, events::messageChangedEvent::TYPE_FLAGS, nums); for (std::list ::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