diff options
Diffstat (limited to 'src/messaging/imap/IMAPFolder.cpp')
-rw-r--r-- | src/messaging/imap/IMAPFolder.cpp | 1561 |
1 files changed, 1561 insertions, 0 deletions
diff --git a/src/messaging/imap/IMAPFolder.cpp b/src/messaging/imap/IMAPFolder.cpp new file mode 100644 index 00000000..5bbfa148 --- /dev/null +++ b/src/messaging/imap/IMAPFolder.cpp @@ -0,0 +1,1561 @@ +// +// 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/IMAPFolder.hpp" + +#include "vmime/messaging/imap/IMAPStore.hpp" +#include "vmime/messaging/imap/IMAPParser.hpp" +#include "vmime/messaging/imap/IMAPMessage.hpp" +#include "vmime/messaging/imap/IMAPUtils.hpp" +#include "vmime/messaging/imap/IMAPConnection.hpp" + +#include "vmime/message.hpp" + +#include "vmime/exception.hpp" +#include "vmime/utility/smartPtr.hpp" + +#include <algorithm> +#include <sstream> + + +namespace vmime { +namespace messaging { +namespace imap { + + +IMAPFolder::IMAPFolder(const folder::path& path, IMAPStore* store, const int type, const int flags) + : m_store(store), m_connection(m_store->connection()), m_path(path), + m_name(path.isEmpty() ? folder::path::component("") : path.getLastComponent()), m_mode(-1), + m_open(false), m_type(type), m_flags(flags), m_messageCount(0), m_uidValidity(0) +{ + m_store->registerFolder(this); +} + + +IMAPFolder::~IMAPFolder() +{ + if (m_store) + { + if (m_open) + close(false); + + m_store->unregisterFolder(this); + } + else if (m_open) + { + delete (m_connection); + onClose(); + } +} + + +const int IMAPFolder::getMode() const +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + return (m_mode); +} + + +const int IMAPFolder::getType() +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + // Root folder + if (m_path.isEmpty()) + { + return (TYPE_CONTAINS_FOLDERS); + } + else + { + if (m_type == TYPE_UNDEFINED) + testExistAndGetType(); + + return (m_type); + } +} + + +const int IMAPFolder::getFlags() +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + // Root folder + if (m_path.isEmpty()) + { + return (FLAG_CHILDREN | FLAG_NO_OPEN); + } + else + { + if (m_flags == FLAG_UNDEFINED) + testExistAndGetType(); + + return (m_flags); + } +} + + +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) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + + // Open a connection for this folder + IMAPConnection* connection = + new IMAPConnection(m_store, m_store->oneTimeAuthenticator()); + + 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::ostringstream oss; + + if (mode == MODE_READ_ONLY) + oss << "EXAMINE "; + else + oss << "SELECT "; + + oss << IMAPUtils::quoteString(IMAPUtils::pathToString + (connection->hierarchySeparator(), getFullPath())); + + connection->send(true, oss.str(), true); + + // Read the response + utility::auto_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", + 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("SELECT", + connection->getParser()->lastLine(), "invalid response"); + } + + const IMAPParser::response_data* responseData = (*it)->response_data(); + + // OK Untagged responses: UNSEEN, PERMANENTFLAGS, UIDVALIDITY (optional) + if (responseData->resp_cond_state()) + { + const IMAPParser::resp_text_code* code = + responseData->resp_cond_state()->resp_text()->resp_text_code(); + + if (code != NULL) + { + switch (code->type()) + { + case IMAPParser::resp_text_code::UIDVALIDITY: + + m_uidValidity = code->nz_number()->value(); + 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: + { + m_type = IMAPUtils::folderTypeFromFlags + (responseData->mailbox_data()->mailbox_flag_list()); + + m_flags = IMAPUtils::folderFlagsFromFlags + (responseData->mailbox_data()->mailbox_flag_list()); + + break; + } + case IMAPParser::mailbox_data::EXISTS: + { + m_messageCount = responseData->mailbox_data()->number()->value(); + break; + } + case IMAPParser::mailbox_data::RECENT: + { + // TODO + break; + } + + } + } + } + + // Check for access mode (read-only or read-write) + const IMAPParser::resp_text_code* respTextCode = resp->response_done()-> + response_tagged()->resp_cond_state()->resp_text()->resp_text_code(); + + 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&) + { + delete (connection); + throw; + } +} + + +void IMAPFolder::close(const bool expunge) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + 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(); + + oldConnection->send(true, "CLOSE", true); + } + + // Close this folder connection + oldConnection->disconnect(); + + // Now use default store connection + m_connection = m_store->connection(); + + m_open = false; + m_mode = -1; + + m_uidValidity = 0; + + onClose(); + + delete (oldConnection); +} + + +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 int type) +{ + if (!m_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 (!m_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 (type & TYPE_CONTAINS_FOLDERS) + mailbox += m_connection->hierarchySeparator(); + + std::ostringstream oss; + oss << "CREATE " << IMAPUtils::quoteString(mailbox); + + m_connection->send(true, oss.str(), true); + + + utility::auto_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", + m_connection->getParser()->lastLine(), "bad response"); + } + + // Notify folder created + events::folderEvent event(this, events::folderEvent::TYPE_CREATED, m_path, m_path); + notifyFolder(event); +} + + +const bool IMAPFolder::exists() +{ + if (!isOpen() && !m_store) + throw exceptions::illegal_state("Store disconnected"); + + return (testExistAndGetType() != TYPE_UNDEFINED); +} + + +const int IMAPFolder::testExistAndGetType() +{ + m_type = TYPE_UNDEFINED; + + // 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 + + std::ostringstream oss; + oss << "LIST \"\" "; + oss << IMAPUtils::quoteString(IMAPUtils::pathToString + (m_connection->hierarchySeparator(), getFullPath())); + + m_connection->send(true, oss.str(), true); + + + utility::auto_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", + m_connection->getParser()->lastLine(), "bad response"); + } + + // Check whether the result mailbox list contains this folder + 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("LIST", + m_connection->getParser()->lastLine(), "invalid response"); + } + + const IMAPParser::mailbox_data* mailboxData = + (*it)->response_data()->mailbox_data(); + + // We are only interested in responses of type "LIST" + if (mailboxData != NULL && mailboxData->type() == IMAPParser::mailbox_data::LIST) + { + // Get the folder type/flags at the same time + m_type = IMAPUtils::folderTypeFromFlags + (mailboxData->mailbox_list()->mailbox_flag_list()); + + m_flags = IMAPUtils::folderFlagsFromFlags + (mailboxData->mailbox_list()->mailbox_flag_list()); + } + } + + return (m_type); +} + + +const bool IMAPFolder::isOpen() const +{ + return (m_open); +} + + +message* IMAPFolder::getMessage(const int num) +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + if (num < 1 || num > m_messageCount) + throw exceptions::message_not_found(); + + return new IMAPMessage(this, num); +} + + +std::vector <message*> IMAPFolder::getMessages(const int from, const int to) +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + std::vector <message*> v; + + for (int i = from ; i <= to ; ++i) + v.push_back(new IMAPMessage(this, i)); + + return (v); +} + + +std::vector <message*> IMAPFolder::getMessages(const std::vector <int>& nums) +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + std::vector <message*> v; + + for (std::vector <int>::const_iterator it = nums.begin() ; it != nums.end() ; ++it) + v.push_back(new IMAPMessage(this, *it)); + + return (v); +} + + +const int IMAPFolder::getMessageCount() +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + return (m_messageCount); +} + + +folder* IMAPFolder::getFolder(const folder::path::component& name) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + + return new IMAPFolder(m_path / name, m_store); +} + + +std::vector <folder*> IMAPFolder::getFolders(const bool recursive) +{ + if (!isOpen() && !m_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 + + std::ostringstream oss; + oss << "LIST "; + oss << IMAPUtils::quoteString + (IMAPUtils::pathToString(m_connection->hierarchySeparator(), getFullPath())); + + if (recursive) + oss << " *"; + else + oss << " %"; + + m_connection->send(true, oss.str(), true); + + + utility::auto_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", m_connection->getParser()->lastLine(), "bad response"); + } + + const std::vector <IMAPParser::continue_req_or_response_data*>& respDataList = + resp->continue_req_or_response_data(); + + + std::vector <folder*> v; + + try + { + 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("LIST", + m_connection->getParser()->lastLine(), "invalid response"); + } + + const IMAPParser::mailbox_data* mailboxData = + (*it)->response_data()->mailbox_data(); + + if (mailboxData == NULL || mailboxData->type() != IMAPParser::mailbox_data::LIST) + continue; + + // Get folder path + const class IMAPParser::mailbox* 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 + const class IMAPParser::mailbox_flag_list* mailbox_flag_list = + mailboxData->mailbox_list()->mailbox_flag_list(); + + v.push_back(new IMAPFolder(path, m_store, + IMAPUtils::folderTypeFromFlags(mailbox_flag_list), + IMAPUtils::folderFlagsFromFlags(mailbox_flag_list))); + } + } + } + catch (std::exception&) + { + for (std::vector <folder*>::iterator it = v.begin() ; it != v.end() ; ++it) + delete (*it); + + throw; + } + + return (v); +} + + +void IMAPFolder::fetchMessages(std::vector <message*>& msg, const int options, + progressionListener* progress) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + const int total = msg.size(); + int current = 0; + + if (progress) + progress->start(total); + + for (std::vector <message*>::iterator it = msg.begin() ; + it != msg.end() ; ++it) + { + dynamic_cast <IMAPMessage*>(*it)->fetch(this, options); + + if (progress) + progress->progress(++current, total); + } + + if (progress) + progress->stop(total); +} + + +void IMAPFolder::fetchMessage(message* msg, const int options) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + dynamic_cast <IMAPMessage*>(msg)->fetch(this, options); +} + + +const int IMAPFolder::getFetchCapabilities() const +{ + return (FETCH_ENVELOPE | FETCH_CONTENT_INFO | FETCH_STRUCTURE | + FETCH_FLAGS | FETCH_SIZE | FETCH_FULL_HEADER | FETCH_UID); +} + + +folder* IMAPFolder::getParent() +{ + return (m_path.isEmpty() ? NULL : new IMAPFolder(m_path.getParent(), m_store)); +} + + +const store* IMAPFolder::getStore() const +{ + return (m_store); +} + + +store* IMAPFolder::getStore() +{ + return (m_store); +} + + +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 = NULL; +} + + +void IMAPFolder::deleteMessage(const int num) +{ + if (!m_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"); + + // Build the request text + std::ostringstream command; + command << "STORE " << num << " +FLAGS.SILENT (\\Deleted)"; + + // Send the request + m_connection->send(true, command.str(), true); + + // Get the response + utility::auto_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", + m_connection->getParser()->lastLine(), "bad response"); + } + + // Update local flags + for (std::vector <IMAPMessage*>::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + if ((*it)->getNumber() == num && + (*it)->m_flags != message::FLAG_UNDEFINED) + { + (*it)->m_flags |= message::FLAG_DELETED; + } + } + + // Notify message flags changed + std::vector <int> nums; + nums.push_back(num); + + events::messageChangedEvent event(this, events::messageChangedEvent::TYPE_FLAGS, nums); + + notifyMessageChanged(event); +} + + +void IMAPFolder::deleteMessages(const int from, const int to) +{ + if (from < 1 || (to < from && to != -1)) + throw exceptions::invalid_argument(); + + if (!m_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"); + + // Build the request text + std::ostringstream command; + command << "STORE " << from << ":"; + + if (to == -1) command << m_messageCount; + else command << to; + + command << " +FLAGS.SILENT (\\Deleted)"; + + // Send the request + m_connection->send(true, command.str(), true); + + // Get the response + utility::auto_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", + m_connection->getParser()->lastLine(), "bad response"); + } + + // Update local flags + const int to2 = (to == -1) ? m_messageCount : to; + const int count = to - from + 1; + + for (std::vector <IMAPMessage*>::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + if ((*it)->getNumber() >= from && (*it)->getNumber() <= to2 && + (*it)->m_flags != message::FLAG_UNDEFINED) + { + (*it)->m_flags |= message::FLAG_DELETED; + } + } + + // Notify message flags changed + std::vector <int> nums; + nums.resize(count); + + for (int i = from, j = 0 ; i <= to2 ; ++i, ++j) + nums[j] = i; + + events::messageChangedEvent event(this, events::messageChangedEvent::TYPE_FLAGS, nums); + + notifyMessageChanged(event); +} + + +void IMAPFolder::deleteMessages(const std::vector <int>& nums) +{ + if (nums.empty()) + throw exceptions::invalid_argument(); + + if (!m_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"); + + // Sort the list of message numbers + std::vector <int> list; + + list.resize(nums.size()); + std::copy(nums.begin(), nums.end(), list.begin()); + + std::sort(list.begin(), list.end()); + + // Build the request text + std::ostringstream command; + command << "STORE "; + command << IMAPUtils::listToSet(list, m_messageCount, true); + command << " +FLAGS.SILENT (\\Deleted)"; + + // Send the request + m_connection->send(true, command.str(), true); + + // Get the response + utility::auto_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", + m_connection->getParser()->lastLine(), "bad response"); + } + + // Update local flags + for (std::vector <IMAPMessage*>::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + if (std::binary_search(list.begin(), list.end(), (*it)->getNumber())) + { + if ((*it)->m_flags != message::FLAG_UNDEFINED) + (*it)->m_flags |= message::FLAG_DELETED; + } + } + + // Notify message flags changed + events::messageChangedEvent event(this, events::messageChangedEvent::TYPE_FLAGS, list); + + notifyMessageChanged(event); +} + + +void IMAPFolder::setMessageFlags(const int from, const int to, const int flags, const int mode) +{ + if (from < 1 || (to < from && to != -1)) + throw exceptions::invalid_argument(); + + if (!m_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"); + + std::ostringstream oss; + + if (to == -1) + oss << from << ":*"; + else + oss << from << ":" << to; + + setMessageFlags(oss.str(), flags, mode); + + // Update local flags + const int to2 = (to == -1) ? m_messageCount : to; + const int count = to - from + 1; + + switch (mode) + { + case message::FLAG_MODE_ADD: + { + for (std::vector <IMAPMessage*>::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + if ((*it)->getNumber() >= from && (*it)->getNumber() <= to2 && + (*it)->m_flags != message::FLAG_UNDEFINED) + { + (*it)->m_flags |= flags; + } + } + + break; + } + case message::FLAG_MODE_REMOVE: + { + for (std::vector <IMAPMessage*>::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + if ((*it)->getNumber() >= from && (*it)->getNumber() <= to2 && + (*it)->m_flags != message::FLAG_UNDEFINED) + { + (*it)->m_flags &= ~flags; + } + } + + break; + } + default: + case message::FLAG_MODE_SET: + { + for (std::vector <IMAPMessage*>::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + if ((*it)->getNumber() >= from && (*it)->getNumber() <= to2 && + (*it)->m_flags != message::FLAG_UNDEFINED) + { + (*it)->m_flags = flags; + } + } + + break; + } + + } + + // Notify message flags changed + std::vector <int> nums; + nums.resize(count); + + for (int i = from, j = 0 ; i <= to2 ; ++i, ++j) + nums[j] = i; + + events::messageChangedEvent event(this, events::messageChangedEvent::TYPE_FLAGS, nums); + + notifyMessageChanged(event); +} + + +void IMAPFolder::setMessageFlags(const std::vector <int>& nums, const int flags, const int mode) +{ + if (!m_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"); + + // Sort the list of message numbers + std::vector <int> list; + + list.resize(nums.size()); + std::copy(nums.begin(), nums.end(), list.begin()); + + std::sort(list.begin(), list.end()); + + // Delegates call + setMessageFlags(IMAPUtils::listToSet(list, m_messageCount, true), flags, mode); + + // Update local flags + switch (mode) + { + case message::FLAG_MODE_ADD: + { + for (std::vector <IMAPMessage*>::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + if (std::binary_search(list.begin(), list.end(), (*it)->getNumber()) && + (*it)->m_flags != message::FLAG_UNDEFINED) + { + (*it)->m_flags |= flags; + } + } + + break; + } + case message::FLAG_MODE_REMOVE: + { + for (std::vector <IMAPMessage*>::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + if (std::binary_search(list.begin(), list.end(), (*it)->getNumber()) && + (*it)->m_flags != message::FLAG_UNDEFINED) + { + (*it)->m_flags &= ~flags; + } + } + + break; + } + default: + case message::FLAG_MODE_SET: + { + for (std::vector <IMAPMessage*>::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + if (std::binary_search(list.begin(), list.end(), (*it)->getNumber()) && + (*it)->m_flags != message::FLAG_UNDEFINED) + { + (*it)->m_flags = flags; + } + } + + break; + } + + } + + // Notify message flags changed + events::messageChangedEvent event(this, events::messageChangedEvent::TYPE_FLAGS, nums); + + notifyMessageChanged(event); +} + + +void IMAPFolder::setMessageFlags(const string& set, const int flags, const int mode) +{ + // Build the request text + std::ostringstream command; + command << "STORE " << set; + + switch (mode) + { + case message::FLAG_MODE_ADD: command << " +FLAGS.SILENT "; break; + case message::FLAG_MODE_REMOVE: command << " -FLAGS.SILENT "; break; + default: + case message::FLAG_MODE_SET: command << " FLAGS.SILENT "; break; + } + + const string flagList = IMAPUtils::messageFlagList(flags); + + if (!flagList.empty()) + { + command << flagList; + + // Send the request + m_connection->send(true, command.str(), true); + + // Get the response + utility::auto_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", + m_connection->getParser()->lastLine(), "bad response"); + } + } +} + + +void IMAPFolder::addMessage(vmime::message* msg, const int flags, + vmime::datetime* date, progressionListener* progress) +{ + std::ostringstream oss; + utility::outputStreamAdapter ossAdapter(oss); + + msg->generate(ossAdapter); + + const std::string& str = oss.str(); + utility::inputStreamStringAdapter strAdapter(str); + + addMessage(strAdapter, str.length(), flags, date, progress); +} + + +void IMAPFolder::addMessage(utility::inputStream& is, const int size, const int flags, + vmime::datetime* date, progressionListener* progress) +{ + if (!m_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"); + + // Build the request text + std::ostringstream command; + command << "APPEND " << IMAPUtils::quoteString(IMAPUtils::pathToString + (m_connection->hierarchySeparator(), getFullPath())) << ' '; + + const string flagList = IMAPUtils::messageFlagList(flags); + + if (flags != message::FLAG_UNDEFINED && !flagList.empty()) + { + command << flagList; + command << ' '; + } + + if (date != NULL) + { + command << IMAPUtils::dateTime(*date); + command << ' '; + } + + command << '{' << size << '}'; + + // Send the request + m_connection->send(true, command.str(), true); + + // Get the response + utility::auto_ptr <IMAPParser::response> resp(m_connection->readResponse()); + + bool ok = false; + const std::vector <IMAPParser::continue_req_or_response_data*>& respList + = resp->continue_req_or_response_data(); + + for (std::vector <IMAPParser::continue_req_or_response_data*>::const_iterator + it = respList.begin() ; !ok && (it != respList.end()) ; ++it) + { + if ((*it)->continue_req()) + ok = true; + } + + if (!ok) + { + throw exceptions::command_error("APPEND", + m_connection->getParser()->lastLine(), "bad response"); + } + + // Send message data + const int total = size; + int current = 0; + + if (progress) + progress->start(total); + + char buffer[65536]; + + while (!is.eof()) + { + // Read some data from the input stream + const int read = is.read(buffer, sizeof(buffer)); + current += read; + + // Put read data into socket output stream + m_connection->sendRaw(buffer, read); + + // Notify progression + if (progress) + progress->progress(current, total); + } + + m_connection->send(false, "", true); + + if (progress) + progress->stop(total); + + // Get the response + utility::auto_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", + m_connection->getParser()->lastLine(), "bad response"); + } + + // Notify message added + std::vector <int> nums; + nums.push_back(m_messageCount + 1); + + events::messageCountEvent event(this, events::messageCountEvent::TYPE_ADDED, nums); + + m_messageCount++; + notifyMessageCount(event); + + // Notify folders with the same path + for (std::list <IMAPFolder*>::iterator it = m_store->m_folders.begin() ; + it != m_store->m_folders.end() ; ++it) + { + if ((*it) != this && (*it)->getFullPath() == m_path) + { + events::messageCountEvent event(*it, events::messageCountEvent::TYPE_ADDED, nums); + + (*it)->m_messageCount++; + (*it)->notifyMessageCount(event); + } + } +} + + +void IMAPFolder::expunge() +{ + if (!m_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 + m_connection->send(true, "EXPUNGE", true); + + // Get the response + utility::auto_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", + m_connection->getParser()->lastLine(), "bad response"); + } + + // Update the numbering of the messages + const std::vector <IMAPParser::continue_req_or_response_data*>& respDataList = + resp->continue_req_or_response_data(); + + std::vector <int> nums; + + 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("EXPUNGE", + m_connection->getParser()->lastLine(), "invalid response"); + } + + const IMAPParser::message_data* messageData = + (*it)->response_data()->message_data(); + + // We are only interested in responses of type "EXPUNGE" + if (messageData == NULL || + messageData->type() != IMAPParser::message_data::EXPUNGE) + { + continue; + } + + const int number = messageData->number(); + + nums.push_back(number); + + for (std::vector <IMAPMessage*>::iterator jt = + m_messages.begin() ; jt != m_messages.end() ; ++jt) + { + if ((*jt)->m_num == number) + (*jt)->m_expunged = true; + else if ((*jt)->m_num > number) + (*jt)->m_num--; + } + } + + m_messageCount -= nums.size(); + + // Notify message expunged + events::messageCountEvent event(this, events::messageCountEvent::TYPE_REMOVED, nums); + + notifyMessageCount(event); + + // Notify folders with the same path + for (std::list <IMAPFolder*>::iterator it = m_store->m_folders.begin() ; + it != m_store->m_folders.end() ; ++it) + { + if ((*it) != this && (*it)->getFullPath() == m_path) + { + (*it)->m_messageCount = m_messageCount; + + events::messageCountEvent event(*it, events::messageCountEvent::TYPE_REMOVED, nums); + + (*it)->notifyMessageCount(event); + } + } +} + + +void IMAPFolder::rename(const folder::path& newPath) +{ + if (!m_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 (!m_store->isValidFolderName(newPath.getLastComponent())) + throw exceptions::invalid_folder_name(); + + // Build the request text + std::ostringstream command; + command << "RENAME "; + command << IMAPUtils::quoteString(IMAPUtils::pathToString + (m_connection->hierarchySeparator(), getFullPath())) << " "; + command << IMAPUtils::quoteString(IMAPUtils::pathToString + (m_connection->hierarchySeparator(), newPath)); + + // Send the request + m_connection->send(true, command.str(), true); + + // Get the response + utility::auto_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", + m_connection->getParser()->lastLine(), "bad response"); + } + + // Notify folder renamed + folder::path oldPath(m_path); + + m_path = newPath; + m_name = newPath.getLastComponent(); + + events::folderEvent event(this, events::folderEvent::TYPE_RENAMED, oldPath, newPath); + notifyFolder(event); + + // Notify folders with the same path and sub-folders + for (std::list <IMAPFolder*>::iterator it = m_store->m_folders.begin() ; + it != m_store->m_folders.end() ; ++it) + { + if ((*it) != this && (*it)->getFullPath() == oldPath) + { + (*it)->m_path = newPath; + (*it)->m_name = newPath.getLastComponent(); + + events::folderEvent event(*it, events::folderEvent::TYPE_RENAMED, oldPath, newPath); + (*it)->notifyFolder(event); + } + else if ((*it) != this && oldPath.isParentOf((*it)->getFullPath())) + { + folder::path oldPath((*it)->m_path); + + (*it)->m_path.renameParent(oldPath, newPath); + + events::folderEvent event(*it, events::folderEvent::TYPE_RENAMED, oldPath, (*it)->m_path); + (*it)->notifyFolder(event); + } + } +} + + +void IMAPFolder::copyMessage(const folder::path& dest, const int num) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + // Construct set + std::ostringstream set; + set << num; + + // Delegate message copy + copyMessages(set.str(), dest); + + // Notify message count changed + std::vector <int> nums; + nums.push_back(num); + + events::messageCountEvent event(this, events::messageCountEvent::TYPE_ADDED, nums); + + for (std::list <IMAPFolder*>::iterator it = m_store->m_folders.begin() ; + it != m_store->m_folders.end() ; ++it) + { + if ((*it)->getFullPath() == dest) + { + (*it)->m_messageCount++; + (*it)->notifyMessageCount(event); + } + } +} + + +void IMAPFolder::copyMessages(const folder::path& dest, const int from, const int to) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + else if (from < 1 || (to < from && to != -1)) + throw exceptions::invalid_argument(); + + // Construct set + std::ostringstream set; + + if (to == -1) + set << from << ":*"; + else + set << from << ":" << to; + + // Delegate message copy + copyMessages(set.str(), dest); + + // Notify message count changed + const int to2 = (to == -1) ? m_messageCount : to; + const int count = to - from + 1; + + std::vector <int> nums; + nums.resize(count); + + for (int i = from, j = 0 ; i <= to2 ; ++i, ++j) + nums[j] = i; + + events::messageCountEvent event(this, events::messageCountEvent::TYPE_ADDED, nums); + + for (std::list <IMAPFolder*>::iterator it = m_store->m_folders.begin() ; + it != m_store->m_folders.end() ; ++it) + { + if ((*it)->getFullPath() == dest) + { + (*it)->m_messageCount += count; + (*it)->notifyMessageCount(event); + } + } +} + + +void IMAPFolder::copyMessages(const folder::path& dest, const std::vector <int>& nums) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + // Delegate message copy + copyMessages(IMAPUtils::listToSet(nums, m_messageCount), dest); + + // Notify message count changed + const int count = nums.size(); + + events::messageCountEvent event(this, events::messageCountEvent::TYPE_ADDED, nums); + + for (std::list <IMAPFolder*>::iterator it = m_store->m_folders.begin() ; + it != m_store->m_folders.end() ; ++it) + { + if ((*it)->getFullPath() == dest) + { + (*it)->m_messageCount += count; + (*it)->notifyMessageCount(event); + } + } +} + + +void IMAPFolder::copyMessages(const string& set, const folder::path& dest) +{ + // Build the request text + std::ostringstream command; + command << "COPY " << set << " "; + command << IMAPUtils::quoteString(IMAPUtils::pathToString + (m_connection->hierarchySeparator(), dest)); + + // Send the request + m_connection->send(true, command.str(), true); + + // Get the response + utility::auto_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", + m_connection->getParser()->lastLine(), "bad response"); + } +} + + +void IMAPFolder::status(int& count, int& unseen) +{ + count = 0; + unseen = 0; + + // Build the request text + std::ostringstream command; + command << "STATUS "; + command << IMAPUtils::quoteString(IMAPUtils::pathToString + (m_connection->hierarchySeparator(), getFullPath())); + command << "(MESSAGES UNSEEN)"; + + // Send the request + m_store->m_connection->send(true, command.str(), true); + + // Get the response + utility::auto_ptr <IMAPParser::response> resp(m_store->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", + m_store->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("STATUS", + m_store->m_connection->getParser()->lastLine(), "invalid response"); + } + + const IMAPParser::response_data* responseData = (*it)->response_data(); + + if (responseData->mailbox_data() && + responseData->mailbox_data()->type() == IMAPParser::mailbox_data::STATUS) + { + const std::vector <IMAPParser::status_info*>& statusList = + responseData->mailbox_data()->status_info_list(); + + for (std::vector <IMAPParser::status_info*>::const_iterator + jt = statusList.begin() ; jt != statusList.end() ; ++jt) + { + switch ((*jt)->status_att()->type()) + { + case IMAPParser::status_att::MESSAGES: + + count = (*jt)->number()->value(); + break; + + case IMAPParser::status_att::UNSEEN: + + unseen = (*jt)->number()->value(); + break; + + default: + + break; + } + } + } + } + + // Notify message count changed (new messages) + if (m_messageCount != count) + { + const int oldCount = m_messageCount; + + m_messageCount = count; + + if (count > oldCount) + { + std::vector <int> nums; + nums.reserve(count - oldCount); + + for (int i = oldCount + 1, j = 0 ; i <= count ; ++i, ++j) + nums[j] = i; + + events::messageCountEvent event(this, events::messageCountEvent::TYPE_ADDED, nums); + + notifyMessageCount(event); + + // Notify folders with the same path + for (std::list <IMAPFolder*>::iterator it = m_store->m_folders.begin() ; + it != m_store->m_folders.end() ; ++it) + { + if ((*it) != this && (*it)->getFullPath() == m_path) + { + (*it)->m_messageCount = count; + + events::messageCountEvent event(*it, events::messageCountEvent::TYPE_ADDED, nums); + + (*it)->notifyMessageCount(event); + } + } + } + } +} + + +} // imap +} // messaging +} // vmime |