// // VMime library (http://www.vmime.org) // Copyright (C) 2002 Vincent Richard <vincent@vmime.org> // // 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 3 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., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // // Linking this library statically or dynamically with other modules is making // a combined work based on this library. Thus, the terms and conditions of // the GNU General Public License cover the whole combination. // #include "vmime/config.hpp" #if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP #include "vmime/net/imap/IMAPFolder.hpp" #include "vmime/net/imap/IMAPStore.hpp" #include "vmime/net/imap/IMAPParser.hpp" #include "vmime/net/imap/IMAPMessage.hpp" #include "vmime/net/imap/IMAPUtils.hpp" #include "vmime/net/imap/IMAPConnection.hpp" #include "vmime/net/imap/IMAPFolderStatus.hpp" #include "vmime/net/imap/IMAPCommand.hpp" #include "vmime/message.hpp" #include "vmime/exception.hpp" #include "vmime/utility/outputStreamAdapter.hpp" #include <algorithm> #include <sstream> namespace vmime { namespace net { namespace imap { IMAPFolder::IMAPFolder( const folder::path& path, const shared_ptr <IMAPStore>& store, const shared_ptr <folderAttributes>& attribs ) : m_store(store), m_connection(store->connection()), m_path(path), m_name(path.isEmpty() ? folder::path::component("") : path.getLastComponent()), m_mode(-1), m_open(false), m_attribs(attribs) { store->registerFolder(this); m_status = make_shared <IMAPFolderStatus>(); } IMAPFolder::~IMAPFolder() { try { shared_ptr <IMAPStore> store = m_store.lock(); if (store) { if (m_open) { close(false); } store->unregisterFolder(this); } else if (m_open) { m_connection = null; onClose(); } } catch (...) { // Don't throw in destructor } } int IMAPFolder::getMode() const { if (!isOpen()) { throw exceptions::illegal_state("Folder not open"); } return m_mode; } const folderAttributes IMAPFolder::getAttributes() { // Root folder if (m_path.isEmpty()) { folderAttributes attribs; attribs.setType(folderAttributes::TYPE_CONTAINS_FOLDERS); attribs.setFlags(folderAttributes::FLAG_HAS_CHILDREN | folderAttributes::FLAG_NO_OPEN); return attribs; } else { if (!m_attribs) { testExistAndGetType(); } return *m_attribs; } } const folder::path::component IMAPFolder::getName() const { return m_name; } const folder::path IMAPFolder::getFullPath() const { return m_path; } void IMAPFolder::open(const int mode, bool failIfModeIsNotAvailable) { shared_ptr <IMAPStore> store = m_store.lock(); if (!store) { throw exceptions::illegal_state("Store disconnected"); } // Ensure this folder is not already open in the same session for (std::list <IMAPFolder*>::iterator it = store->m_folders.begin() ; it != store->m_folders.end() ; ++it) { if ((*it) != this && (*it)->getFullPath() == m_path) { throw exceptions::folder_already_open(); } } // Open a connection for this folder shared_ptr <IMAPConnection> connection = make_shared <IMAPConnection>(store, store->getAuthenticator()); try { connection->connect(); // Emit the "SELECT" command // // Example: C: A142 SELECT INBOX // S: * 172 EXISTS // S: * 1 RECENT // S: * OK [UNSEEN 12] Message 12 is first unseen // S: * OK [UIDVALIDITY 3857529045] UIDs valid // S: * FLAGS (\Answered \Flagged \Deleted \Seen \Draft) // S: * OK [PERMANENTFLAGS (\Deleted \Seen \*)] Limited // S: A142 OK [READ-WRITE] SELECT completed std::vector <string> selectParams; if (m_connection->hasCapability("CONDSTORE")) { selectParams.push_back("CONDSTORE"); } IMAPCommand::SELECT( mode == MODE_READ_ONLY, IMAPUtils::pathToString(connection->hierarchySeparator(), getFullPath()), selectParams )->send(connection); // Read the response scoped_ptr <IMAPParser::response> resp(connection->readResponse()); if (resp->isBad() || resp->response_done->response_tagged-> resp_cond_state->status != IMAPParser::resp_cond_state::OK) { throw exceptions::command_error("SELECT", resp->getErrorLog(), "bad response"); } auto &respDataList = resp->continue_req_or_response_data; for (auto it = respDataList.begin() ; it != respDataList.end() ; ++it) { auto *responseData = (*it)->response_data.get(); if (!responseData) { throw exceptions::command_error("SELECT", resp->getErrorLog(), "invalid response"); } // OK Untagged responses: UNSEEN, PERMANENTFLAGS, UIDVALIDITY (optional) if (responseData->resp_cond_state) { auto *code = responseData->resp_cond_state->resp_text->resp_text_code.get(); if (code) { switch (code->type) { case IMAPParser::resp_text_code::NOMODSEQ: connection->disableMODSEQ(); break; default: break; } } // Untagged responses: FLAGS, EXISTS, RECENT (required) } else if (responseData->mailbox_data) { switch (responseData->mailbox_data->type) { default: break; case IMAPParser::mailbox_data::FLAGS: { if (!m_attribs) { m_attribs = make_shared <folderAttributes>(); } IMAPUtils::mailboxFlagsToFolderAttributes( connection, m_path, *responseData->mailbox_data->mailbox_flag_list, *m_attribs ); break; } } } } processStatusUpdate(resp.get()); // Check for access mode (read-only or read-write) auto *respTextCode = resp->response_done->response_tagged->resp_cond_state->resp_text->resp_text_code.get(); if (respTextCode) { const int openMode = (respTextCode->type == IMAPParser::resp_text_code::READ_WRITE) ? MODE_READ_WRITE : MODE_READ_ONLY; if (failIfModeIsNotAvailable && mode == MODE_READ_WRITE && openMode == MODE_READ_ONLY) { throw exceptions::operation_not_supported(); } } m_connection = connection; m_open = true; m_mode = mode; } catch (std::exception&) { throw; } } void IMAPFolder::close(const bool expunge) { shared_ptr <IMAPStore> store = m_store.lock(); if (!store) { throw exceptions::illegal_state("Store disconnected"); } if (!isOpen()) { throw exceptions::illegal_state("Folder not open"); } shared_ptr <IMAPConnection> oldConnection = m_connection; // Emit the "CLOSE" command to expunge messages marked // as deleted (this is fastest than "EXPUNGE") if (expunge) { if (m_mode == MODE_READ_ONLY) { throw exceptions::operation_not_supported(); } IMAPCommand::CLOSE()->send(oldConnection); } // Close this folder connection oldConnection->disconnect(); // Now use default store connection m_connection = m_store.lock()->connection(); m_open = false; m_mode = -1; m_status = make_shared <IMAPFolderStatus>(); onClose(); } void IMAPFolder::onClose() { for (std::vector <IMAPMessage*>::iterator it = m_messages.begin() ; it != m_messages.end() ; ++it) { (*it)->onFolderClosed(); } m_messages.clear(); } void IMAPFolder::create(const folderAttributes& attribs) { shared_ptr <IMAPStore> store = m_store.lock(); if (!store) { throw exceptions::illegal_state("Store disconnected"); } else if (isOpen()) { throw exceptions::illegal_state("Folder is open"); } else if (exists()) { throw exceptions::illegal_state("Folder already exists"); } else if (!store->isValidFolderName(m_name)) { throw exceptions::invalid_folder_name(); } // Emit the "CREATE" command // // Example: C: A003 CREATE owatagusiam/ // S: A003 OK CREATE completed // C: A004 CREATE owatagusiam/blurdybloop // S: A004 OK CREATE completed string mailbox = IMAPUtils::pathToString (m_connection->hierarchySeparator(), getFullPath()); if (attribs.getType() & folderAttributes::TYPE_CONTAINS_FOLDERS) { mailbox += m_connection->hierarchySeparator(); } std::vector <string> createParams; if (attribs.getSpecialUse() != folderAttributes::SPECIALUSE_NONE) { if (!m_connection->hasCapability("CREATE-SPECIAL-USE")) { throw exceptions::operation_not_supported(); } // C: t2 CREATE MySpecial (USE (\Drafts \Sent)) std::ostringstream oss; oss << "USE ("; switch (attribs.getSpecialUse()) { case folderAttributes::SPECIALUSE_NONE: // should not happen case folderAttributes::SPECIALUSE_ALL: oss << "\\All"; break; case folderAttributes::SPECIALUSE_ARCHIVE: oss << "\\Archive"; break; case folderAttributes::SPECIALUSE_DRAFTS: oss << "\\Drafts"; break; case folderAttributes::SPECIALUSE_FLAGGED: oss << "\\Flagged"; break; case folderAttributes::SPECIALUSE_JUNK: oss << "\\Junk"; break; case folderAttributes::SPECIALUSE_SENT: oss << "\\Sent"; break; case folderAttributes::SPECIALUSE_TRASH: oss << "\\Trash"; break; case folderAttributes::SPECIALUSE_IMPORTANT: oss << "\\Important"; break; } oss << ")"; createParams.push_back(oss.str()); } IMAPCommand::CREATE(mailbox, createParams)->send(m_connection); scoped_ptr <IMAPParser::response> resp(m_connection->readResponse()); if (resp->isBad() || resp->response_done->response_tagged-> resp_cond_state->status != IMAPParser::resp_cond_state::OK) { throw exceptions::command_error("CREATE", resp->getErrorLog(), "bad response"); } // Notify folder created shared_ptr <events::folderEvent> event = make_shared <events::folderEvent>( dynamicCast <folder>(shared_from_this()), events::folderEvent::TYPE_CREATED, m_path, m_path ); notifyFolder(event); } void IMAPFolder::destroy() { shared_ptr <IMAPStore> store = m_store.lock(); if (!store) { throw exceptions::illegal_state("Store disconnected"); } if (isOpen()) { throw exceptions::illegal_state("Folder is open"); } const string mailbox = IMAPUtils::pathToString( m_connection->hierarchySeparator(), getFullPath() ); IMAPCommand::DELETE(mailbox)->send(m_connection); scoped_ptr <IMAPParser::response> resp(m_connection->readResponse()); if (resp->isBad() || resp->response_done->response_tagged-> resp_cond_state->status != IMAPParser::resp_cond_state::OK) { throw exceptions::command_error("DELETE", resp->getErrorLog(), "bad response"); } // Notify folder deleted shared_ptr <events::folderEvent> event = make_shared <events::folderEvent>( dynamicCast <folder>(shared_from_this()), events::folderEvent::TYPE_DELETED, m_path, m_path ); notifyFolder(event); } bool IMAPFolder::exists() { shared_ptr <IMAPStore> store = m_store.lock(); if (!isOpen() && !store) { throw exceptions::illegal_state("Store disconnected"); } return testExistAndGetType() != -1; } int IMAPFolder::testExistAndGetType() { // To test whether a folder exists, we simple list it using // the "LIST" command, and there should be one unique mailbox // with this name... // // Eg. Test whether '/foo/bar' exists // // C: a005 list "" foo/bar // S: * LIST (\NoSelect) "/" foo/bar // S: a005 OK LIST completed // // ==> OK, exists // // Test whether '/foo/bar/zap' exists // // C: a005 list "" foo/bar/zap // S: a005 OK LIST completed // // ==> NO, does not exist IMAPCommand::LIST( "", IMAPUtils::pathToString( m_connection->hierarchySeparator(), getFullPath() ) )->send(m_connection); scoped_ptr <IMAPParser::response> resp(m_connection->readResponse()); if (resp->isBad() || resp->response_done->response_tagged-> resp_cond_state->status != IMAPParser::resp_cond_state::OK) { throw exceptions::command_error("LIST", resp->getErrorLog(), "bad response"); } // Check whether the result mailbox list contains this folder auto& respDataList = resp->continue_req_or_response_data; folderAttributes attribs; attribs.setType(-1); for (auto it = respDataList.begin() ; it != respDataList.end() ; ++it) { if (!(*it)->response_data) { throw exceptions::command_error("LIST", resp->getErrorLog(), "invalid response"); } auto *mailboxData = (*it)->response_data->mailbox_data.get(); // We are only interested in responses of type "LIST" if (mailboxData && mailboxData->type == IMAPParser::mailbox_data::LIST) { // Get the folder type/flags at the same time IMAPUtils::mailboxFlagsToFolderAttributes( m_connection, m_path, *mailboxData->mailbox_list->mailbox_flag_list, attribs ); } } m_attribs = make_shared <folderAttributes>(attribs); return m_attribs->getType(); } bool IMAPFolder::isOpen() const { return m_open; } shared_ptr <message> IMAPFolder::getMessage(const size_t num) { if (!isOpen()) { throw exceptions::illegal_state("Folder not open"); } if (num < 1 || num > m_status->getMessageCount()) { throw exceptions::message_not_found(); } return make_shared <IMAPMessage>(dynamicCast <IMAPFolder>(shared_from_this()), num); } std::vector <shared_ptr <message> > IMAPFolder::getMessages(const messageSet& msgs) { if (!isOpen()) { throw exceptions::illegal_state("Folder not open"); } if (msgs.isEmpty()) { return std::vector <shared_ptr <message> >(); } std::vector <shared_ptr <message> > messages; // Sequence number message set: // C: . FETCH uuuu1,uuuu2,uuuu3 UID // S: * nnnn1 FETCH (UID uuuu1) // S: * nnnn2 FETCH (UID uuuu2) // S: * nnnn3 FETCH (UID uuuu3) // S: . OK FETCH completed // UID message set: // C: . UID FETCH uuuu1,uuuu2,uuuu3 UID // S: * nnnn1 FETCH (UID uuuu1) // S: * nnnn2 FETCH (UID uuuu2) // S: * nnnn3 FETCH (UID uuuu3) // S: . OK UID FETCH completed std::vector <string> params; params.push_back("UID"); IMAPCommand::FETCH(msgs, params)->send(m_connection); // Get the response scoped_ptr <IMAPParser::response> resp(m_connection->readResponse()); if (resp->isBad() || resp->response_done->response_tagged-> resp_cond_state->status != IMAPParser::resp_cond_state::OK) { throw exceptions::command_error("UID FETCH ... UID", resp->getErrorLog(), "bad response"); } // Process the response auto &respDataList = resp->continue_req_or_response_data; for (auto it = respDataList.begin() ; it != respDataList.end() ; ++it) { if (!(*it)->response_data) { throw exceptions::command_error("UID FETCH ... UID", resp->getErrorLog(), "invalid response"); } auto *messageData = (*it)->response_data->message_data.get(); // We are only interested in responses of type "FETCH" if (!messageData || messageData->type != IMAPParser::message_data::FETCH) { continue; } // Find UID in message attributes const size_t msgNum = messageData->number; message::uid msgUID; for (auto &att : messageData->msg_att->items) { if (att->type == IMAPParser::msg_att_item::UID) { msgUID = att->uniqueid->value; break; } } if (!msgUID.empty()) { shared_ptr <IMAPFolder> thisFolder = dynamicCast <IMAPFolder>(shared_from_this()); messages.push_back(make_shared <IMAPMessage>(thisFolder, msgNum, msgUID)); } } return messages; } size_t IMAPFolder::getMessageCount() { if (!isOpen()) { throw exceptions::illegal_state("Folder not open"); } return m_status->getMessageCount(); } vmime_uint32 IMAPFolder::getUIDValidity() const { if (!isOpen()) { throw exceptions::illegal_state("Folder not open"); } return m_status->getUIDValidity(); } vmime_uint64 IMAPFolder::getHighestModSequence() const { if (!isOpen()) { throw exceptions::illegal_state("Folder not open"); } return m_status->getHighestModSeq(); } shared_ptr <folder> IMAPFolder::getFolder(const folder::path::component& name) { shared_ptr <IMAPStore> store = m_store.lock(); if (!store) { throw exceptions::illegal_state("Store disconnected"); } return make_shared <IMAPFolder>(m_path / name, store, shared_ptr <folderAttributes>()); } std::vector <shared_ptr <folder> > IMAPFolder::getFolders(const bool recursive) { shared_ptr <IMAPStore> store = m_store.lock(); if (!isOpen() && !store) { throw exceptions::illegal_state("Store disconnected"); } // Eg. List folders in '/foo/bar' // // C: a005 list "foo/bar" * // S: * LIST (\NoSelect) "/" foo/bar // S: * LIST (\NoInferiors) "/" foo/bar/zap // S: a005 OK LIST completed shared_ptr <IMAPCommand> cmd; const string pathString = IMAPUtils::pathToString( m_connection->hierarchySeparator(), getFullPath() ); if (recursive) { cmd = IMAPCommand::LIST(pathString, "*"); } else { cmd = IMAPCommand::LIST( pathString.empty() ? "" : (pathString + m_connection->hierarchySeparator()), "%" ); } cmd->send(m_connection); scoped_ptr <IMAPParser::response> resp(m_connection->readResponse()); if (resp->isBad() || resp->response_done->response_tagged-> resp_cond_state->status != IMAPParser::resp_cond_state::OK) { throw exceptions::command_error("LIST", resp->getErrorLog(), "bad response"); } auto &respDataList = resp->continue_req_or_response_data; std::vector <shared_ptr <folder> > v; for (auto it = respDataList.begin() ; it != respDataList.end() ; ++it) { if (!(*it)->response_data) { throw exceptions::command_error("LIST", resp->getErrorLog(), "invalid response"); } auto *mailboxData = (*it)->response_data->mailbox_data.get(); if (!mailboxData || mailboxData->type != IMAPParser::mailbox_data::LIST) { continue; } // Get folder path auto &mailbox = mailboxData->mailbox_list->mailbox; folder::path path = IMAPUtils::stringToPath( mailboxData->mailbox_list->quoted_char, mailbox->name ); if (recursive || m_path.isDirectParentOf(path)) { // Append folder to list shared_ptr <folderAttributes> attribs = make_shared <folderAttributes>(); IMAPUtils::mailboxFlagsToFolderAttributes( m_connection, path, *mailboxData->mailbox_list->mailbox_flag_list, *attribs ); v.push_back(make_shared <IMAPFolder>(path, store, attribs)); } } return v; } void IMAPFolder::fetchMessages( std::vector <shared_ptr <message> >& msg, const fetchAttributes& options, utility::progressListener* progress ) { shared_ptr <IMAPStore> store = m_store.lock(); if (!store) { throw exceptions::illegal_state("Store disconnected"); } else if (!isOpen()) { throw exceptions::illegal_state("Folder not open"); } if (msg.empty()) { return; } // Build message numbers list std::vector <size_t> list; list.reserve(msg.size()); std::map <size_t, shared_ptr <IMAPMessage> > numberToMsg; for (std::vector <shared_ptr <message> >::iterator it = msg.begin() ; it != msg.end() ; ++it) { list.push_back((*it)->getNumber()); numberToMsg[(*it)->getNumber()] = dynamicCast <IMAPMessage>(*it); } // Send the request IMAPUtils::buildFetchCommand( m_connection, messageSet::byNumber(list), options )->send(m_connection); // Get the response scoped_ptr <IMAPParser::response> resp(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", resp->getErrorLog(), "bad response"); } auto &respDataList = resp->continue_req_or_response_data; const size_t total = msg.size(); size_t current = 0; if (progress) { progress->start(total); } try { for (auto it = respDataList.begin() ; it != respDataList.end() ; ++it) { if (!(*it)->response_data) { throw exceptions::command_error("FETCH", resp->getErrorLog(), "invalid response"); } auto *messageData = (*it)->response_data->message_data.get(); // We are only interested in responses of type "FETCH" if (!messageData || messageData->type != IMAPParser::message_data::FETCH) { continue; } // Process fetch response for this message const size_t num = messageData->number; std::map <size_t, shared_ptr <IMAPMessage> >::iterator msg = numberToMsg.find(num); if (msg != numberToMsg.end()) { (*msg).second->processFetchResponse(options, *messageData); if (progress) { progress->progress(++current, total); } } } } catch (...) { if (progress) { progress->stop(total); } throw; } if (progress) { progress->stop(total); } processStatusUpdate(resp.get()); } void IMAPFolder::fetchMessage(const shared_ptr <message>& msg, const fetchAttributes& options) { std::vector <shared_ptr <message> > msgs; msgs.push_back(msg); fetchMessages(msgs, options, /* progress */ NULL); } std::vector <shared_ptr <message> > IMAPFolder::getAndFetchMessages( const messageSet& msgs, const fetchAttributes& attribs ) { shared_ptr <IMAPStore> store = m_store.lock(); if (!store) { throw exceptions::illegal_state("Store disconnected"); } else if (!isOpen()) { throw exceptions::illegal_state("Folder not open"); } if (msgs.isEmpty()) { return std::vector <shared_ptr <message> >(); } // Ensure we also get the UID for each message fetchAttributes attribsWithUID(attribs); attribsWithUID.add(fetchAttributes::UID); // Send the request IMAPUtils::buildFetchCommand(m_connection, msgs, attribsWithUID)->send(m_connection); // Get the response scoped_ptr <IMAPParser::response> resp(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", resp->getErrorLog(), "bad response"); } auto &respDataList = resp->continue_req_or_response_data; std::vector <shared_ptr <message> > messages; for (auto it = respDataList.begin() ; it != respDataList.end() ; ++it) { if (!(*it)->response_data) { throw exceptions::command_error("FETCH", resp->getErrorLog(), "invalid response"); } auto *messageData = (*it)->response_data->message_data.get(); // We are only interested in responses of type "FETCH" if (!messageData || messageData->type != IMAPParser::message_data::FETCH) { continue; } // Get message number const size_t msgNum = messageData->number; // Get message UID message::uid msgUID; for (auto &att : messageData->msg_att->items) { if (att->type == IMAPParser::msg_att_item::UID) { msgUID = att->uniqueid->value; break; } } // Create a new message reference shared_ptr <IMAPFolder> thisFolder = dynamicCast <IMAPFolder>(shared_from_this()); shared_ptr <IMAPMessage> msg = make_shared <IMAPMessage>(thisFolder, msgNum, msgUID); messages.push_back(msg); // Process fetch response for this message msg->processFetchResponse(attribsWithUID, *messageData); } processStatusUpdate(resp.get()); return messages; } int IMAPFolder::getFetchCapabilities() const { return fetchAttributes::ENVELOPE | fetchAttributes::CONTENT_INFO | fetchAttributes::STRUCTURE | fetchAttributes::FLAGS | fetchAttributes::SIZE | fetchAttributes::FULL_HEADER | fetchAttributes::UID | fetchAttributes::IMPORTANCE; } shared_ptr <folder> IMAPFolder::getParent() { if (m_path.isEmpty()) { return null; } else { return make_shared <IMAPFolder>( m_path.getParent(), m_store.lock(), shared_ptr <folderAttributes>() ); } } shared_ptr <const store> IMAPFolder::getStore() const { return m_store.lock(); } shared_ptr <store> IMAPFolder::getStore() { return m_store.lock(); } void IMAPFolder::registerMessage(IMAPMessage* msg) { m_messages.push_back(msg); } void IMAPFolder::unregisterMessage(IMAPMessage* msg) { std::vector <IMAPMessage*>::iterator it = std::find(m_messages.begin(), m_messages.end(), msg); if (it != m_messages.end()) { m_messages.erase(it); } } void IMAPFolder::onStoreDisconnected() { m_store.reset(); } void IMAPFolder::deleteMessages(const messageSet& msgs) { shared_ptr <IMAPStore> store = m_store.lock(); if (msgs.isEmpty()) { throw exceptions::invalid_argument(); } if (!store) { throw exceptions::illegal_state("Store disconnected"); } else if (!isOpen()) { throw exceptions::illegal_state("Folder not open"); } else if (m_mode == MODE_READ_ONLY) { throw exceptions::illegal_state("Folder is read-only"); } // Send the request IMAPCommand::STORE( msgs, message::FLAG_MODE_ADD, IMAPUtils::messageFlagList(message::FLAG_DELETED) )->send(m_connection); // Get the response scoped_ptr <IMAPParser::response> resp(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", resp->getErrorLog(), "bad response"); } processStatusUpdate(resp.get()); } void IMAPFolder::setMessageFlags( const messageSet& msgs, const int flags, const int mode ) { const std::vector <string> flagList = IMAPUtils::messageFlagList(flags); if ((mode == message::FLAG_MODE_SET) || !flagList.empty()) { // Send the request IMAPCommand::STORE(msgs, mode, flagList)->send(m_connection); // Get the response scoped_ptr <IMAPParser::response> resp(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", resp->getErrorLog(), "bad response"); } processStatusUpdate(resp.get()); } } messageSet IMAPFolder::addMessage( const shared_ptr <vmime::message>& msg, const int flags, vmime::datetime* date, utility::progressListener* progress ) { std::ostringstream oss; utility::outputStreamAdapter ossAdapter(oss); msg->generate(ossAdapter); const string& str = oss.str(); utility::inputStreamStringAdapter strAdapter(str); return addMessage(strAdapter, str.length(), flags, date, progress); } messageSet IMAPFolder::addMessage( utility::inputStream& is, const size_t size, const int flags, vmime::datetime* date, utility::progressListener* progress ) { shared_ptr <IMAPStore> store = m_store.lock(); if (!store) { throw exceptions::illegal_state("Store disconnected"); } else if (!isOpen()) { throw exceptions::illegal_state("Folder not open"); } else if (m_mode == MODE_READ_ONLY) { throw exceptions::illegal_state("Folder is read-only"); } // Send the request IMAPCommand::APPEND( IMAPUtils::pathToString(m_connection->hierarchySeparator(), getFullPath()), IMAPUtils::messageFlagList(flags), date, size )->send(m_connection); // Get the response scoped_ptr <IMAPParser::response> resp(m_connection->readResponse()); bool ok = false; auto &respList = resp->continue_req_or_response_data; for (auto it = respList.begin() ; !ok && (it != respList.end()) ; ++it) { if ((*it)->continue_req) { ok = true; } } if (!ok) { throw exceptions::command_error("APPEND", resp->getErrorLog(), "bad response"); } processStatusUpdate(resp.get()); // Send message data const size_t total = size; size_t current = 0; if (progress) { progress->start(total); } const size_t blockSize = std::min( is.getBlockSize(), static_cast <size_t>(m_connection->getSocket()->getBlockSize()) ); std::vector <byte_t> vbuffer(blockSize); byte_t* buffer = &vbuffer.front(); while (!is.eof()) { // Read some data from the input stream const size_t read = is.read(buffer, blockSize); current += read; // Put read data into socket output stream m_connection->sendRaw(buffer, read); // Notify progress if (progress) { progress->progress(current, total); } } m_connection->sendRaw(utility::stringUtils::bytesFromString("\r\n"), 2); if (m_connection->getTracer()) { m_connection->getTracer()->traceSendBytes(current); } if (progress) { progress->stop(total); } // Get the response scoped_ptr <IMAPParser::response> finalResp(m_connection->readResponse()); if (finalResp->isBad() || finalResp->response_done->response_tagged-> resp_cond_state->status != IMAPParser::resp_cond_state::OK) { throw exceptions::command_error("APPEND", resp->getErrorLog(), "bad response"); } processStatusUpdate(finalResp.get()); auto *respTextCode = finalResp->response_done->response_tagged->resp_cond_state->resp_text->resp_text_code.get(); if (respTextCode && respTextCode->type == IMAPParser::resp_text_code::APPENDUID) { return IMAPUtils::buildMessageSet(*respTextCode->uid_set); } return messageSet::empty(); } void IMAPFolder::expunge() { shared_ptr <IMAPStore> store = m_store.lock(); if (!store) { throw exceptions::illegal_state("Store disconnected"); } else if (!isOpen()) { throw exceptions::illegal_state("Folder not open"); } else if (m_mode == MODE_READ_ONLY) { throw exceptions::illegal_state("Folder is read-only"); } // Send the request IMAPCommand::EXPUNGE()->send(m_connection); // Get the response scoped_ptr <IMAPParser::response> resp(m_connection->readResponse()); if (resp->isBad() || resp->response_done->response_tagged-> resp_cond_state->status != IMAPParser::resp_cond_state::OK) { throw exceptions::command_error("EXPUNGE", resp->getErrorLog(), "bad response"); } processStatusUpdate(resp.get()); } void IMAPFolder::rename(const folder::path& newPath) { shared_ptr <IMAPStore> store = m_store.lock(); if (!store) { throw exceptions::illegal_state("Store disconnected"); } else if (m_path.isEmpty() || newPath.isEmpty()) { throw exceptions::illegal_operation("Cannot rename root folder"); } else if (m_path.getSize() == 1 && m_name.getBuffer() == "INBOX") { throw exceptions::illegal_operation("Cannot rename 'INBOX' folder"); } else if (!store->isValidFolderName(newPath.getLastComponent())) { throw exceptions::invalid_folder_name(); } // Send the request IMAPCommand::RENAME( IMAPUtils::pathToString(m_connection->hierarchySeparator(), getFullPath()), IMAPUtils::pathToString(m_connection->hierarchySeparator(), newPath) )->send(m_connection); // Get the response scoped_ptr <IMAPParser::response> resp(m_connection->readResponse()); if (resp->isBad() || resp->response_done->response_tagged-> resp_cond_state->status != IMAPParser::resp_cond_state::OK) { throw exceptions::command_error("RENAME", resp->getErrorLog(), "bad response"); } // Notify folder renamed folder::path oldPath(m_path); m_path = newPath; m_name = newPath.getLastComponent(); shared_ptr <events::folderEvent> event = make_shared <events::folderEvent>( dynamicCast <folder>(shared_from_this()), events::folderEvent::TYPE_RENAMED, oldPath, newPath ); notifyFolder(event); // Notify sub-folders for (std::list <IMAPFolder*>::iterator it = store->m_folders.begin() ; it != store->m_folders.end() ; ++it) { if ((*it) != this && oldPath.isParentOf((*it)->getFullPath())) { folder::path oldPath((*it)->m_path); (*it)->m_path.renameParent(oldPath, newPath); shared_ptr <events::folderEvent> event = make_shared <events::folderEvent>( dynamicCast <folder>((*it)->shared_from_this()), events::folderEvent::TYPE_RENAMED, oldPath, (*it)->m_path ); (*it)->notifyFolder(event); } } processStatusUpdate(resp.get()); } messageSet IMAPFolder::copyMessages(const folder::path& dest, const messageSet& set) { shared_ptr <IMAPStore> store = m_store.lock(); if (!store) { throw exceptions::illegal_state("Store disconnected"); } else if (!isOpen()) { throw exceptions::illegal_state("Folder not open"); } // Send the request IMAPCommand::COPY( set, IMAPUtils::pathToString(m_connection->hierarchySeparator(), dest) )->send(m_connection); // Get the response scoped_ptr <IMAPParser::response> resp(m_connection->readResponse()); if (resp->isBad() || resp->response_done->response_tagged-> resp_cond_state->status != IMAPParser::resp_cond_state::OK) { throw exceptions::command_error("COPY", resp->getErrorLog(), "bad response"); } processStatusUpdate(resp.get()); auto *respTextCode = resp->response_done->response_tagged->resp_cond_state->resp_text->resp_text_code.get(); if (respTextCode && respTextCode->type == IMAPParser::resp_text_code::COPYUID) { return IMAPUtils::buildMessageSet(*respTextCode->uid_set2); } return messageSet::empty(); } void IMAPFolder::status(size_t& count, size_t& unseen) { count = 0; unseen = 0; shared_ptr <folderStatus> status = getStatus(); count = status->getMessageCount(); unseen = status->getUnseenCount(); } shared_ptr <folderStatus> IMAPFolder::getStatus() { shared_ptr <IMAPStore> store = m_store.lock(); if (!store) { throw exceptions::illegal_state("Store disconnected"); } // Build the attributes list std::vector <string> attribs; attribs.push_back("MESSAGES"); attribs.push_back("UNSEEN"); attribs.push_back("UIDNEXT"); attribs.push_back("UIDVALIDITY"); if (m_connection->hasCapability("CONDSTORE")) { attribs.push_back("HIGHESTMODSEQ"); } // Send the request IMAPCommand::STATUS( IMAPUtils::pathToString(m_connection->hierarchySeparator(), getFullPath()), attribs )->send(m_connection); // Get the response scoped_ptr <IMAPParser::response> resp(m_connection->readResponse()); if (resp->isBad() || resp->response_done->response_tagged-> resp_cond_state->status != IMAPParser::resp_cond_state::OK) { throw exceptions::command_error("STATUS", resp->getErrorLog(), "bad response"); } auto &respDataList = resp->continue_req_or_response_data; for (auto it = respDataList.begin() ; it != respDataList.end() ; ++it) { if ((*it)->response_data) { auto &responseData = (*it)->response_data; if (responseData->mailbox_data && responseData->mailbox_data->type == IMAPParser::mailbox_data::STATUS) { shared_ptr <IMAPFolderStatus> status = make_shared <IMAPFolderStatus>(); status->updateFromResponse(*responseData->mailbox_data); m_status->updateFromResponse(*responseData->mailbox_data); return status; } } } throw exceptions::command_error("STATUS", resp->getErrorLog(), "invalid response"); } void IMAPFolder::noop() { shared_ptr <IMAPStore> store = m_store.lock(); if (!store) { throw exceptions::illegal_state("Store disconnected"); } IMAPCommand::NOOP()->send(m_connection); scoped_ptr <IMAPParser::response> resp(m_connection->readResponse()); if (resp->isBad() || resp->response_done->response_tagged-> resp_cond_state->status != IMAPParser::resp_cond_state::OK) { throw exceptions::command_error("NOOP", resp->getErrorLog()); } processStatusUpdate(resp.get()); } std::vector <size_t> IMAPFolder::getMessageNumbersStartingOnUID(const message::uid& uid) { // Send the request std::ostringstream uidSearchKey; uidSearchKey.imbue(std::locale::classic()); uidSearchKey << "UID " << uid << ":*"; std::vector <string> searchKeys; searchKeys.push_back(uidSearchKey.str()); IMAPCommand::SEARCH(searchKeys, /* charset */ NULL)->send(m_connection); // Get the response scoped_ptr <IMAPParser::response> resp(m_connection->readResponse()); if (resp->isBad() || resp->response_done->response_tagged->resp_cond_state->status != IMAPParser::resp_cond_state::OK) { throw exceptions::command_error("SEARCH", resp->getErrorLog(), "bad response"); } auto& respDataList = resp->continue_req_or_response_data; std::vector <size_t> seqNumbers; for (auto it = respDataList.begin() ; it != respDataList.end() ; ++it) { if (!(*it)->response_data) { throw exceptions::command_error("SEARCH", resp->getErrorLog(), "invalid response"); } auto *mailboxData = (*it)->response_data->mailbox_data.get(); // We are only interested in responses of type "SEARCH" if (!mailboxData || mailboxData->type != IMAPParser::mailbox_data::SEARCH) { continue; } for (auto &nzn : mailboxData->search_nz_number_list) { seqNumbers.push_back(nzn->value); } } processStatusUpdate(resp.get()); return seqNumbers; } void IMAPFolder::processStatusUpdate(const IMAPParser::response* resp) { std::vector <shared_ptr <events::event> > events; shared_ptr <IMAPFolderStatus> oldStatus = vmime::clone(m_status); int expungedMessageCount = 0; // Process tagged response if (resp->response_done && resp->response_done->response_tagged && resp->response_done->response_tagged->resp_cond_state->resp_text->resp_text_code) { m_status->updateFromResponse( *resp->response_done->response_tagged->resp_cond_state->resp_text->resp_text_code ); } // Process untagged responses for (auto it = resp->continue_req_or_response_data.begin() ; it != resp->continue_req_or_response_data.end() ; ++it) { if ((*it)->response_data && (*it)->response_data->resp_cond_state && (*it)->response_data->resp_cond_state->resp_text->resp_text_code) { m_status->updateFromResponse( *(*it)->response_data->resp_cond_state->resp_text->resp_text_code ); } else if ((*it)->response_data && (*it)->response_data->mailbox_data) { m_status->updateFromResponse(*(*it)->response_data->mailbox_data); // Update folder attributes, if available if ((*it)->response_data->mailbox_data->type == IMAPParser::mailbox_data::LIST) { folderAttributes attribs; IMAPUtils::mailboxFlagsToFolderAttributes( m_connection, m_path, *(*it)->response_data->mailbox_data->mailbox_list->mailbox_flag_list, attribs ); m_attribs = make_shared <folderAttributes>(attribs); } } else if ((*it)->response_data && (*it)->response_data->message_data) { auto* msgData = (*it)->response_data->message_data.get(); const size_t msgNumber = msgData->number; if (msgData->type == IMAPParser::message_data::FETCH) { // Message changed for (std::vector <IMAPMessage*>::iterator mit = m_messages.begin() ; mit != m_messages.end() ; ++mit) { if ((*mit)->getNumber() == msgNumber) { (*mit)->processFetchResponse(/* options */ 0, *msgData); } } events.push_back( make_shared <events::messageChangedEvent>( dynamicCast <folder>(shared_from_this()), events::messageChangedEvent::TYPE_FLAGS, std::vector <size_t>(1, msgNumber) ) ); } else if (msgData->type == IMAPParser::message_data::EXPUNGE) { // A message has been expunged, renumber messages for (std::vector <IMAPMessage*>::iterator jt = m_messages.begin() ; jt != m_messages.end() ; ++jt) { if ((*jt)->getNumber() == msgNumber) { (*jt)->setExpunged(); } else if ((*jt)->getNumber() > msgNumber) { (*jt)->renumber((*jt)->getNumber() - 1); } } events.push_back( make_shared <events::messageCountEvent>( dynamicCast <folder>(shared_from_this()), events::messageCountEvent::TYPE_REMOVED, std::vector <size_t>(1, msgNumber) ) ); expungedMessageCount++; } } } // New messages arrived if (m_status->getMessageCount() > oldStatus->getMessageCount() - expungedMessageCount) { std::vector <size_t> newMessageNumbers; for (size_t msgNumber = oldStatus->getMessageCount() - expungedMessageCount ; msgNumber <= m_status->getMessageCount() ; ++msgNumber) { newMessageNumbers.push_back(msgNumber); } events.push_back( make_shared <events::messageCountEvent>( dynamicCast <folder>(shared_from_this()), events::messageCountEvent::TYPE_ADDED, newMessageNumbers ) ); } // Dispatch notifications for (std::vector <shared_ptr <events::event> >::iterator evit = events.begin() ; evit != events.end() ; ++evit) { notifyEvent(*evit); } } } // imap } // net } // vmime #endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP