From 0bba2794fc73785a07ca7990c615a77f8a3065eb Mon Sep 17 00:00:00 2001 From: Vincent Richard Date: Sun, 3 Apr 2005 08:00:52 +0000 Subject: Moved IMAP, POP3, maildir and SMTP files to separate namespaces. --- src/messaging/IMAPConnection.cpp | 263 ----- src/messaging/IMAPFolder.cpp | 1559 ----------------------------- src/messaging/IMAPMessage.cpp | 841 ---------------- src/messaging/IMAPStore.cpp | 292 ------ src/messaging/IMAPTag.cpp | 97 -- src/messaging/IMAPUtils.cpp | 553 ----------- src/messaging/POP3Folder.cpp | 818 ---------------- src/messaging/POP3Message.cpp | 216 ----- src/messaging/POP3Store.cpp | 628 ------------ src/messaging/SMTPTransport.cpp | 591 ----------- src/messaging/builtinServices.inl | 16 +- src/messaging/imap/IMAPConnection.cpp | 265 +++++ src/messaging/imap/IMAPFolder.cpp | 1561 ++++++++++++++++++++++++++++++ src/messaging/imap/IMAPMessage.cpp | 843 ++++++++++++++++ src/messaging/imap/IMAPStore.cpp | 294 ++++++ src/messaging/imap/IMAPTag.cpp | 99 ++ src/messaging/imap/IMAPUtils.cpp | 555 +++++++++++ src/messaging/maildir/maildirFolder.cpp | 1350 ++++++++++++++++++++++++++ src/messaging/maildir/maildirMessage.cpp | 505 ++++++++++ src/messaging/maildir/maildirStore.cpp | 234 +++++ src/messaging/maildir/maildirUtils.cpp | 194 ++++ src/messaging/maildirFolder.cpp | 1348 -------------------------- src/messaging/maildirMessage.cpp | 503 ---------- src/messaging/maildirStore.cpp | 232 ----- src/messaging/maildirUtils.cpp | 192 ---- src/messaging/pop3/POP3Folder.cpp | 820 ++++++++++++++++ src/messaging/pop3/POP3Message.cpp | 218 +++++ src/messaging/pop3/POP3Store.cpp | 630 ++++++++++++ src/messaging/smtp/SMTPTransport.cpp | 593 ++++++++++++ 29 files changed, 8169 insertions(+), 8141 deletions(-) delete mode 100644 src/messaging/IMAPConnection.cpp delete mode 100644 src/messaging/IMAPFolder.cpp delete mode 100644 src/messaging/IMAPMessage.cpp delete mode 100644 src/messaging/IMAPStore.cpp delete mode 100644 src/messaging/IMAPTag.cpp delete mode 100644 src/messaging/IMAPUtils.cpp delete mode 100644 src/messaging/POP3Folder.cpp delete mode 100644 src/messaging/POP3Message.cpp delete mode 100644 src/messaging/POP3Store.cpp delete mode 100644 src/messaging/SMTPTransport.cpp create mode 100644 src/messaging/imap/IMAPConnection.cpp create mode 100644 src/messaging/imap/IMAPFolder.cpp create mode 100644 src/messaging/imap/IMAPMessage.cpp create mode 100644 src/messaging/imap/IMAPStore.cpp create mode 100644 src/messaging/imap/IMAPTag.cpp create mode 100644 src/messaging/imap/IMAPUtils.cpp create mode 100644 src/messaging/maildir/maildirFolder.cpp create mode 100644 src/messaging/maildir/maildirMessage.cpp create mode 100644 src/messaging/maildir/maildirStore.cpp create mode 100644 src/messaging/maildir/maildirUtils.cpp delete mode 100644 src/messaging/maildirFolder.cpp delete mode 100644 src/messaging/maildirMessage.cpp delete mode 100644 src/messaging/maildirStore.cpp delete mode 100644 src/messaging/maildirUtils.cpp create mode 100644 src/messaging/pop3/POP3Folder.cpp create mode 100644 src/messaging/pop3/POP3Message.cpp create mode 100644 src/messaging/pop3/POP3Store.cpp create mode 100644 src/messaging/smtp/SMTPTransport.cpp (limited to 'src') diff --git a/src/messaging/IMAPConnection.cpp b/src/messaging/IMAPConnection.cpp deleted file mode 100644 index fa0e6af8..00000000 --- a/src/messaging/IMAPConnection.cpp +++ /dev/null @@ -1,263 +0,0 @@ -// -// 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/IMAPTag.hpp" -#include "vmime/messaging/IMAPConnection.hpp" -#include "vmime/messaging/IMAPUtils.hpp" -#include "vmime/messaging/IMAPStore.hpp" - -#include "vmime/exception.hpp" -#include "vmime/platformDependant.hpp" - -#include - - -namespace vmime { -namespace messaging { - - -IMAPConnection::IMAPConnection(IMAPStore* store, authenticator* auth) - : m_store(store), m_auth(auth), m_socket(NULL), m_parser(NULL), m_tag(NULL), - m_hierarchySeparator('\0'), m_state(STATE_NONE), m_timeoutHandler(NULL) -{ -} - - -IMAPConnection::~IMAPConnection() -{ - if (isConnected()) - disconnect(); - else if (m_socket) - internalDisconnect(); - - delete (m_tag); - delete (m_parser); -} - - -void IMAPConnection::connect() -{ - if (isConnected()) - throw exceptions::already_connected(); - - m_state = STATE_NONE; - m_hierarchySeparator = '\0'; - - const string address = m_store->getSession()->getProperties()[m_store->getInfos().getPropertyPrefix() + "server.address"]; - const port_t port = m_store->getSession()->getProperties().getProperty(m_store->getInfos().getPropertyPrefix() + "server.port", m_store->getInfos().getDefaultPort()); - - // Create the time-out handler - if (m_store->getSession()->getProperties().hasProperty - (m_store->getInfos().getPropertyPrefix() + "timeout.factory")) - { - timeoutHandlerFactory* tof = platformDependant::getHandler()-> - getTimeoutHandlerFactory(m_store->getSession()->getProperties() - [m_store->getInfos().getPropertyPrefix() + "timeout.factory"]); - - m_timeoutHandler = tof->create(); - } - - // Create and connect the socket - socketFactory* sf = platformDependant::getHandler()->getSocketFactory - (m_store->getSession()->getProperties().getProperty - (m_store->getInfos().getPropertyPrefix() + "server.socket-factory", string("default"))); - - m_socket = sf->create(); - m_socket->connect(address, port); - - delete (m_tag); - m_tag = new IMAPTag(); - - delete (m_parser); - m_parser = new IMAPParser(m_tag, m_socket, m_timeoutHandler); - - - setState(STATE_NON_AUTHENTICATED); - - - // Connection greeting - // - // eg: C: - // --- S: * OK mydomain.org IMAP4rev1 v12.256 server ready - - utility::auto_ptr greet(m_parser->readGreeting()); - - if (greet->resp_cond_bye()) - { - internalDisconnect(); - throw exceptions::connection_greeting_error(m_parser->lastLine()); - } - else if (greet->resp_cond_auth()->condition() != IMAPParser::resp_cond_auth::PREAUTH) - { - const authenticationInfos auth = m_auth->requestAuthInfos(); - - // TODO: other authentication methods - - send(true, "LOGIN " + IMAPUtils::quoteString(auth.getUsername()) - + " " + IMAPUtils::quoteString(auth.getPassword()), true); - - utility::auto_ptr resp(m_parser->readResponse()); - - if (resp->isBad()) - { - internalDisconnect(); - throw exceptions::command_error("LOGIN", m_parser->lastLine()); - } - else if (resp->response_done()->response_tagged()-> - resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) - { - internalDisconnect(); - throw exceptions::authentication_error(m_parser->lastLine()); - } - } - - // Get the hierarchy separator character - initHierarchySeparator(); - - // Switch to state "Authenticated" - setState(STATE_AUTHENTICATED); -} - - -const bool IMAPConnection::isConnected() const -{ - return (m_socket && m_socket->isConnected() && - (m_state == STATE_AUTHENTICATED || m_state == STATE_SELECTED)); -} - - -void IMAPConnection::disconnect() -{ - if (!isConnected()) - throw exceptions::not_connected(); - - internalDisconnect(); -} - - -void IMAPConnection::internalDisconnect() -{ - send(true, "LOGOUT", true); - - m_socket->disconnect(); - - delete (m_socket); - m_socket = NULL; - - delete (m_timeoutHandler); - m_timeoutHandler = NULL; - - m_state = STATE_LOGOUT; -} - - -void IMAPConnection::initHierarchySeparator() -{ - send(true, "LIST \"\" \"\"", true); - - utility::auto_ptr resp(m_parser->readResponse()); - - if (resp->isBad() || resp->response_done()->response_tagged()-> - resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) - { - internalDisconnect(); - throw exceptions::command_error("LIST", m_parser->lastLine(), "bad response"); - } - - const std::vector & respDataList = - resp->continue_req_or_response_data(); - - if (respDataList.size() < 1 || respDataList[0]->response_data() == NULL) - { - internalDisconnect(); - throw exceptions::command_error("LIST", m_parser->lastLine(), "unexpected response"); - } - - const IMAPParser::mailbox_data* mailboxData = - static_cast (respDataList[0]->response_data())-> - mailbox_data(); - - if (mailboxData == NULL || mailboxData->type() != IMAPParser::mailbox_data::LIST) - { - internalDisconnect(); - throw exceptions::command_error("LIST", m_parser->lastLine(), "invalid type"); - } - - if (mailboxData->mailbox_list()->quoted_char() == '\0') - { - internalDisconnect(); - throw exceptions::command_error("LIST", m_parser->lastLine(), "no hierarchy separator"); - } - - m_hierarchySeparator = mailboxData->mailbox_list()->quoted_char(); -} - - -void IMAPConnection::send(bool tag, const string& what, bool end) -{ -#if VMIME_DEBUG - std::ostringstream oss; - - if (tag) - { - ++(*m_tag); - - oss << string(*m_tag); - oss << " "; - } - - oss << what; - - if (end) - oss << "\r\n"; - - m_socket->send(oss.str()); -#else - if (tag) - { - ++(*m_tag); - - m_socket->send(*m_tag); - m_socket->send(" "); - } - - m_socket->send(what); - - if (end) - { - m_socket->send("\r\n"); - } -#endif -} - - -void IMAPConnection::sendRaw(const char* buffer, const int count) -{ - m_socket->sendRaw(buffer, count); -} - - -IMAPParser::response* IMAPConnection::readResponse(IMAPParser::literalHandler* lh) -{ - return (m_parser->readResponse(lh)); -} - - -} // messaging -} // vmime diff --git a/src/messaging/IMAPFolder.cpp b/src/messaging/IMAPFolder.cpp deleted file mode 100644 index 98737165..00000000 --- a/src/messaging/IMAPFolder.cpp +++ /dev/null @@ -1,1559 +0,0 @@ -// -// 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/IMAPFolder.hpp" - -#include "vmime/messaging/IMAPStore.hpp" -#include "vmime/messaging/IMAPParser.hpp" -#include "vmime/messaging/IMAPMessage.hpp" -#include "vmime/messaging/IMAPUtils.hpp" -#include "vmime/messaging/IMAPConnection.hpp" - -#include "vmime/message.hpp" - -#include "vmime/exception.hpp" -#include "vmime/utility/smartPtr.hpp" - -#include -#include - - -namespace vmime { -namespace messaging { - - -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 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 & 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("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 ::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 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 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 & 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("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 IMAPFolder::getMessages(const int from, const int to) -{ - if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); - - std::vector v; - - for (int i = from ; i <= to ; ++i) - v.push_back(new IMAPMessage(this, i)); - - return (v); -} - - -std::vector IMAPFolder::getMessages(const std::vector & nums) -{ - if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); - - std::vector v; - - for (std::vector ::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 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 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 & respDataList = - resp->continue_req_or_response_data(); - - - std::vector v; - - try - { - for (std::vector ::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 ::iterator it = v.begin() ; it != v.end() ; ++it) - delete (*it); - - throw; - } - - return (v); -} - - -void IMAPFolder::fetchMessages(std::vector & 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 ::iterator it = msg.begin() ; - it != msg.end() ; ++it) - { - dynamic_cast (*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 (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 ::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 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 ::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 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 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 ::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 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 & 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 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 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 ::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 ::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 ::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 ::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 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 & 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 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 ::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 ::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 ::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 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 resp(m_connection->readResponse()); - - bool ok = false; - const std::vector & respList - = resp->continue_req_or_response_data(); - - for (std::vector ::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 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 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 ::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 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 & respDataList = - resp->continue_req_or_response_data(); - - std::vector nums; - - for (std::vector ::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 ::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 ::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 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 ::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 nums; - nums.push_back(num); - - events::messageCountEvent event(this, events::messageCountEvent::TYPE_ADDED, nums); - - for (std::list ::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 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 ::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 & 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 ::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 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 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 & 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("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 & statusList = - responseData->mailbox_data()->status_info_list(); - - for (std::vector ::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 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 ::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); - } - } - } - } -} - - -} // messaging -} // vmime diff --git a/src/messaging/IMAPMessage.cpp b/src/messaging/IMAPMessage.cpp deleted file mode 100644 index bfe72159..00000000 --- a/src/messaging/IMAPMessage.cpp +++ /dev/null @@ -1,841 +0,0 @@ -// -// 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 diff --git a/src/messaging/IMAPStore.cpp b/src/messaging/IMAPStore.cpp deleted file mode 100644 index 809957a4..00000000 --- a/src/messaging/IMAPStore.cpp +++ /dev/null @@ -1,292 +0,0 @@ -// -// 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/IMAPStore.hpp" -#include "vmime/messaging/IMAPFolder.hpp" -#include "vmime/messaging/IMAPConnection.hpp" - -#include "vmime/exception.hpp" -#include "vmime/platformDependant.hpp" - -#include - - -namespace vmime { -namespace messaging { - - -#ifndef VMIME_BUILDING_DOC - -// -// IMAPauthenticator: private class used internally -// -// Used to request user credentials only in the first authentication -// and reuse this information the next times -// - -class IMAPauthenticator : public authenticator -{ -public: - - IMAPauthenticator(authenticator* auth) - : m_auth(auth), m_infos(NULL) - { - } - - ~IMAPauthenticator() - { - delete (m_infos); - } - - const authenticationInfos requestAuthInfos() const - { - if (m_infos == NULL) - m_infos = new authenticationInfos(m_auth->requestAuthInfos()); - - return (*m_infos); - } - -private: - - authenticator* m_auth; - mutable authenticationInfos* m_infos; -}; - -#endif // VMIME_BUILDING_DOC - - - -// -// IMAPStore -// - -IMAPStore::IMAPStore(session* sess, authenticator* auth) - : store(sess, getInfosInstance(), auth), - m_connection(NULL), m_oneTimeAuth(NULL) -{ -} - - -IMAPStore::~IMAPStore() -{ - if (isConnected()) - disconnect(); -} - - -authenticator* IMAPStore::oneTimeAuthenticator() -{ - return (m_oneTimeAuth); -} - - -const string IMAPStore::getProtocolName() const -{ - return "imap"; -} - - -folder* IMAPStore::getRootFolder() -{ - if (!isConnected()) - throw exceptions::illegal_state("Not connected"); - - return new IMAPFolder(folder::path(), this); -} - - -folder* IMAPStore::getDefaultFolder() -{ - if (!isConnected()) - throw exceptions::illegal_state("Not connected"); - - return new IMAPFolder(folder::path::component("INBOX"), this); -} - - -folder* IMAPStore::getFolder(const folder::path& path) -{ - if (!isConnected()) - throw exceptions::illegal_state("Not connected"); - - return new IMAPFolder(path, this); -} - - -const bool IMAPStore::isValidFolderName(const folder::path::component& /* name */) const -{ - return true; -} - - -void IMAPStore::connect() -{ - if (isConnected()) - throw exceptions::already_connected(); - - m_oneTimeAuth = new IMAPauthenticator(getAuthenticator()); - - m_connection = new IMAPConnection(this, m_oneTimeAuth); - - try - { - m_connection->connect(); - } - catch (std::exception&) - { - delete (m_connection); - m_connection = NULL; - throw; - } -} - - -const bool IMAPStore::isConnected() const -{ - return (m_connection && m_connection->isConnected()); -} - - -void IMAPStore::disconnect() -{ - if (!isConnected()) - throw exceptions::not_connected(); - - for (std::list ::iterator it = m_folders.begin() ; - it != m_folders.end() ; ++it) - { - (*it)->onStoreDisconnected(); - } - - m_folders.clear(); - - - m_connection->disconnect(); - - delete (m_oneTimeAuth); - m_oneTimeAuth = NULL; - - delete (m_connection); - m_connection = NULL; -} - - -void IMAPStore::noop() -{ - if (!isConnected()) - throw exceptions::not_connected(); - - m_connection->send(true, "NOOP", true); - - utility::auto_ptr 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", m_connection->getParser()->lastLine()); - } -} - - -IMAPConnection* IMAPStore::connection() -{ - return (m_connection); -} - - -void IMAPStore::registerFolder(IMAPFolder* folder) -{ - m_folders.push_back(folder); -} - - -void IMAPStore::unregisterFolder(IMAPFolder* folder) -{ - std::list ::iterator it = std::find(m_folders.begin(), m_folders.end(), folder); - if (it != m_folders.end()) m_folders.erase(it); -} - - -const int IMAPStore::getCapabilities() const -{ - return (CAPABILITY_CREATE_FOLDER | - CAPABILITY_RENAME_FOLDER | - CAPABILITY_ADD_MESSAGE | - CAPABILITY_COPY_MESSAGE | - CAPABILITY_DELETE_MESSAGE | - CAPABILITY_PARTIAL_FETCH | - CAPABILITY_MESSAGE_FLAGS | - CAPABILITY_EXTRACT_PART); -} - - - - -// Service infos - -IMAPStore::_infos IMAPStore::sm_infos; - - -const serviceInfos& IMAPStore::getInfosInstance() -{ - return (sm_infos); -} - - -const serviceInfos& IMAPStore::getInfos() const -{ - return (sm_infos); -} - - -const port_t IMAPStore::_infos::getDefaultPort() const -{ - return (143); -} - - -const string IMAPStore::_infos::getPropertyPrefix() const -{ - return "store.imap."; -} - - -const std::vector IMAPStore::_infos::getAvailableProperties() const -{ - std::vector list; - - // IMAP-specific options - //list.push_back("auth.mechanism"); - - // Common properties - list.push_back("auth.username"); - list.push_back("auth.password"); - - list.push_back("server.address"); - list.push_back("server.port"); - list.push_back("server.socket-factory"); - - list.push_back("timeout.factory"); - - return (list); -} - - -} // messaging -} // vmime diff --git a/src/messaging/IMAPTag.cpp b/src/messaging/IMAPTag.cpp deleted file mode 100644 index f5c669a0..00000000 --- a/src/messaging/IMAPTag.cpp +++ /dev/null @@ -1,97 +0,0 @@ -// -// 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/IMAPTag.hpp" - - -namespace vmime { -namespace messaging { - - -const int IMAPTag::sm_maxNumber = 52 * 10 * 10 * 10; - - -IMAPTag::IMAPTag(const int number) - : m_number(number) -{ - m_tag.resize(4); -} - - -IMAPTag::IMAPTag(const IMAPTag& tag) - : m_number(tag.m_number) -{ - m_tag.resize(4); -} - - -IMAPTag::IMAPTag() - : m_number(0) -{ - m_tag.resize(4); -} - - -IMAPTag& IMAPTag::operator++() -{ - ++m_number; - - if (m_number >= sm_maxNumber) - m_number = 1; - - generate(); - - return (*this); -} - - -const IMAPTag IMAPTag::operator++(int) -{ - IMAPTag old(*this); - operator++(); - return (old); -} - - -const int IMAPTag::number() const -{ - return (m_number); -} - - -IMAPTag::operator string() const -{ - return (m_tag); -} - - -void IMAPTag::generate() -{ - static const char prefixChars[53] = - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - - m_tag[0] = prefixChars[m_number / 1000]; - m_tag[1] = '0' + (m_number % 1000) / 100; - m_tag[2] = '0' + (m_number % 100) / 10; - m_tag[3] = '0' + (m_number % 10); -} - - -} // messaging -} // vmime diff --git a/src/messaging/IMAPUtils.cpp b/src/messaging/IMAPUtils.cpp deleted file mode 100644 index de0c8d30..00000000 --- a/src/messaging/IMAPUtils.cpp +++ /dev/null @@ -1,553 +0,0 @@ -// -// 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/IMAPUtils.hpp" -#include "vmime/messaging/message.hpp" - -#include -#include -#include - - -namespace vmime { -namespace messaging { - - -const string IMAPUtils::quoteString(const string& text) -{ - // - // ATOM_CHAR ::= - // - // atom_specials ::= "(" / ")" / "{" / SPACE / CTL / - // list_wildcards / quoted_specials - // - // list_wildcards ::= "%" / "*" - // - // quoted_specials ::= <"> / "\" - // - // CHAR ::= - // - // CTL ::= - // - - bool needQuoting = text.empty(); - - for (string::const_iterator it = text.begin() ; - !needQuoting && it != text.end() ; ++it) - { - const unsigned char c = *it; - - switch (c) - { - case '(': - case ')': - case '{': - case 0x20: // SPACE - case '%': - case '*': - case '"': - case '\\': - - needQuoting = true; - break; - - default: - - if (c <= 0x1f || c >= 0x7f) - needQuoting = true; - } - } - - if (needQuoting) - { - string quoted; - quoted.reserve((text.length() * 3) / 2 + 2); - - quoted += '"'; - - for (string::const_iterator it = text.begin() ; - !needQuoting && it != text.end() ; ++it) - { - const unsigned char c = *it; - - if (c == '\\' || c == '"') - quoted += '\\'; - - quoted += c; - } - - quoted += '"'; - - return (quoted); - } - else - { - return (text); - } -} - - -const string IMAPUtils::pathToString - (const char hierarchySeparator, const folder::path& path) -{ - string result; - - for (int i = 0 ; i < path.getSize() ; ++i) - { - if (i > 0) result += hierarchySeparator; - result += toModifiedUTF7(hierarchySeparator, path[i]); - } - - return (result); -} - - -const folder::path IMAPUtils::stringToPath - (const char hierarchySeparator, const string& str) -{ - folder::path result; - string::const_iterator begin = str.begin(); - - for (string::const_iterator it = str.begin() ; it != str.end() ; ++it) - { - if (*it == hierarchySeparator) - { - result /= fromModifiedUTF7(string(begin, it)); - begin = it + 1; - } - } - - if (begin != str.end()) - { - result /= fromModifiedUTF7(string(begin, str.end())); - } - - return (result); -} - - -const string IMAPUtils::toModifiedUTF7 - (const char hierarchySeparator, const folder::path::component& text) -{ - // We will replace the hierarchy separator with an equivalent - // UTF-7 sequence, so we compute it here... - const char base64alphabet[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,="; - - const unsigned int hs = static_cast (static_cast (hierarchySeparator)); - - string hsUTF7; - hsUTF7.resize(3); - - hsUTF7[0] = base64alphabet[0]; - hsUTF7[1] = base64alphabet[(hs & 0xF0) >> 4]; - hsUTF7[2] = base64alphabet[(hs & 0x0F) << 2]; - - // Transcode path component to UTF-7 charset. - // WARNING: This may throw "exceptions::charset_conv_error" - const string cvt = text.getConvertedText(charset(charsets::UTF_7)); - - // Transcode to modified UTF-7 (RFC-2060). - string out; - out.reserve((cvt.length() * 3) / 2); - - bool inB64sequence = false; - - for (string::const_iterator it = cvt.begin() ; it != cvt.end() ; ++it) - { - const unsigned char c = *it; - - // Replace hierarchy separator with an equivalent UTF-7 Base64 sequence - if (!inB64sequence && c == hierarchySeparator) - { - out += "&" + hsUTF7 + "-"; - continue; - } - - switch (c) - { - // Beginning of Base64 sequence: replace '+' with '&' - case '+': - { - if (!inB64sequence) - { - inB64sequence = true; - out += '&'; - } - else - { - out += '+'; - } - - break; - } - // End of Base64 sequence - case '-': - { - inB64sequence = false; - out += '-'; - break; - } - // ',' is used instead of '/' in modified Base64 - case '/': - { - out += inB64sequence ? ',' : '/'; - break; - } - // '&' (0x26) is represented by the two-octet sequence "&-" - case '&': - { - if (!inB64sequence) - out += "&-"; - else - out += '&'; - - break; - } - default: - { - out += c; - break; - } - - } - } - - return (out); -} - - -const folder::path::component IMAPUtils::fromModifiedUTF7(const string& text) -{ - // Transcode from modified UTF-7 (RFC-2060). - string out; - out.reserve(text.length()); - - bool inB64sequence = false; - unsigned char prev = 0; - - for (string::const_iterator it = text.begin() ; it != text.end() ; ++it) - { - const unsigned char c = *it; - - switch (c) - { - // Start of Base64 sequence - case '&': - { - if (!inB64sequence) - { - inB64sequence = true; - out += '+'; - } - else - { - out += '&'; - } - - break; - } - // End of Base64 sequence (or "&-" --> "&") - case '-': - { - if (inB64sequence && prev == '&') - out += '&'; - else - out += '-'; - - inB64sequence = false; - break; - } - // ',' is used instead of '/' in modified Base64 - case ',': - { - out += (inB64sequence ? '/' : ','); - break; - } - default: - { - out += c; - break; - } - - } - - prev = c; - } - - // Store it as UTF-8 by default - string cvt; - charset::convert(out, cvt, - charset(charsets::UTF_7), charset(charsets::UTF_8)); - - return (folder::path::component(cvt, charset(charsets::UTF_8))); -} - - -const int IMAPUtils::folderTypeFromFlags(const IMAPParser::mailbox_flag_list* list) -{ - // Get folder type - int type = folder::TYPE_CONTAINS_MESSAGES | folder::TYPE_CONTAINS_FOLDERS; - const std::vector & flags = list->flags(); - - for (std::vector ::const_iterator it = flags.begin() ; - it != flags.end() ; ++it) - { - if ((*it)->type() == IMAPParser::mailbox_flag::NOSELECT) - type &= ~folder::TYPE_CONTAINS_MESSAGES; - } - - if (type & folder::TYPE_CONTAINS_MESSAGES) - type &= ~folder::TYPE_CONTAINS_FOLDERS; - - return (type); -} - - -const int IMAPUtils::folderFlagsFromFlags(const IMAPParser::mailbox_flag_list* list) -{ - // Get folder flags - int folderFlags = folder::FLAG_CHILDREN; - const std::vector & flags = list->flags(); - - for (std::vector ::const_iterator it = flags.begin() ; - it != flags.end() ; ++it) - { - if ((*it)->type() == IMAPParser::mailbox_flag::NOSELECT) - folderFlags |= folder::FLAG_NO_OPEN; - else if ((*it)->type() == IMAPParser::mailbox_flag::NOINFERIORS) - folderFlags &= ~folder::FLAG_CHILDREN; - } - - return (folderFlags); -} - - -const int IMAPUtils::messageFlagsFromFlags(const IMAPParser::flag_list* list) -{ - const std::vector & flagList = list->flags(); - int flags = 0; - - for (std::vector ::const_iterator - it = flagList.begin() ; it != flagList.end() ; ++it) - { - switch ((*it)->type()) - { - case IMAPParser::flag::ANSWERED: - flags |= message::FLAG_REPLIED; - break; - case IMAPParser::flag::FLAGGED: - flags |= message::FLAG_MARKED; - break; - case IMAPParser::flag::DELETED: - flags |= message::FLAG_DELETED; - break; - case IMAPParser::flag::SEEN: - flags |= message::FLAG_SEEN; - break; - - default: - //case IMAPParser::flag::UNKNOWN: - //case IMAPParser::flag::DRAFT: - break; - } - } - - return (flags); -} - - -const string IMAPUtils::messageFlagList(const int flags) -{ - std::vector flagList; - - if (flags & message::FLAG_REPLIED) flagList.push_back("\\Answered"); - if (flags & message::FLAG_MARKED) flagList.push_back("\\Flagged"); - if (flags & message::FLAG_DELETED) flagList.push_back("\\Deleted"); - if (flags & message::FLAG_SEEN) flagList.push_back("\\Seen"); - - if (!flagList.empty()) - { - std::ostringstream res; - res << "("; - - if (flagList.size() >= 2) - { - std::copy(flagList.begin(), flagList.end() - 1, - std::ostream_iterator (res, " ")); - } - - res << *(flagList.end() - 1) << ")"; - - return (res.str()); - } - - return ""; -} - - -// This function builds a "IMAP set" given a list. Try to group consecutive -// message numbers to reduce the list. -// -// Example: -// IN = "1,2,3,4,5,7,8,13,15,16,17" -// OUT = "1:5,7:8,13,15:*" for a mailbox with a total of 17 messages (max = 17) - -const string IMAPUtils::listToSet(const std::vector & list, const int max, - const bool alreadySorted) -{ - // Sort a copy of the list (if not already sorted) - std::vector temp; - - if (!alreadySorted) - { - temp.resize(list.size()); - std::copy(list.begin(), list.end(), temp.begin()); - - std::sort(temp.begin(), temp.end()); - } - - const std::vector & theList = (alreadySorted ? list : temp); - - // Build the set - std::ostringstream res; - int previous = -1, setBegin = -1; - - for (std::vector ::const_iterator it = theList.begin() ; - it != theList.end() ; ++it) - { - const int current = *it; - - if (previous == -1) - { - res << current; - - previous = current; - setBegin = current; - } - else - { - if (current == previous + 1) - { - previous = current; - } - else - { - if (setBegin != previous) - { - res << ":" << previous << "," << current; - - previous = current; - setBegin = current; - } - else - { - if (setBegin != current) // skip duplicates - res << "," << current; - - previous = current; - setBegin = current; - } - } - } - } - - if (previous != setBegin) - { - if (previous == max) - res << ":*"; - else - res << ":" << previous; - } - - return (res.str()); -} - - -const string IMAPUtils::dateTime(const vmime::datetime& date) -{ - std::ostringstream res; - - // date_time ::= <"> date_day_fixed "-" date_month "-" date_year - // SPACE time SPACE zone <"> - // - // time ::= 2digit ":" 2digit ":" 2digit - // ;; Hours minutes seconds - // zone ::= ("+" / "-") 4digit - // ;; Signed four-digit value of hhmm representing - // ;; hours and minutes west of Greenwich - res << '"'; - - // Date - if (date.getDay() < 10) res << ' '; - res << date.getDay(); - - res << '-'; - - static const char* monthNames[12] = - { "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; - - res << monthNames[std::min(std::max(date.getMonth() - 1, 0), 11)]; - - res << '-'; - - if (date.getYear() < 10) res << '0'; - if (date.getYear() < 100) res << '0'; - if (date.getYear() < 1000) res << '0'; - res << date.getYear(); - - res << ' '; - - // Time - if (date.getHour() < 10) res << '0'; - res << date.getHour() << ':'; - - if (date.getMinute() < 10) res << '0'; - res << date.getMinute() << ':'; - - if (date.getSecond() < 10) res << '0'; - res << date.getSecond(); - - res << ' '; - - // Zone - const int zs = (date.getZone() < 0 ? -1 : 1); - const int zh = (date.getZone() * zs) / 60; - const int zm = (date.getZone() * zs) % 60; - - res << (zs < 0 ? '-' : '+'); - - if (zh < 10) res << '0'; - res << zh; - - if (zm < 10) res << '0'; - res << zm; - - res << '"'; - - - return (res.str()); -} - - -} // messaging -} // vmime diff --git a/src/messaging/POP3Folder.cpp b/src/messaging/POP3Folder.cpp deleted file mode 100644 index e0bac1a8..00000000 --- a/src/messaging/POP3Folder.cpp +++ /dev/null @@ -1,818 +0,0 @@ -// -// 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/POP3Folder.hpp" - -#include "vmime/messaging/POP3Store.hpp" -#include "vmime/messaging/POP3Message.hpp" - -#include "vmime/exception.hpp" - - -namespace vmime { -namespace messaging { - - -POP3Folder::POP3Folder(const folder::path& path, POP3Store* store) - : m_store(store), m_path(path), - m_name(path.isEmpty() ? folder::path::component("") : path.getLastComponent()), - m_mode(-1), m_open(false) -{ - m_store->registerFolder(this); -} - - -POP3Folder::~POP3Folder() -{ - if (m_store) - { - if (m_open) - close(false); - - m_store->unregisterFolder(this); - } - else if (m_open) - { - onClose(); - } -} - - -const int POP3Folder::getMode() const -{ - if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); - - return (m_mode); -} - - -const int POP3Folder::getType() -{ - if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); - - if (m_path.isEmpty()) - return (TYPE_CONTAINS_FOLDERS); - else if (m_path.getSize() == 1 && m_path[0].getBuffer() == "INBOX") - return (TYPE_CONTAINS_MESSAGES); - else - throw exceptions::folder_not_found(); -} - - -const int POP3Folder::getFlags() -{ - return (0); -} - - -const folder::path::component POP3Folder::getName() const -{ - return (m_name); -} - - -const folder::path POP3Folder::getFullPath() const -{ - return (m_path); -} - - -void POP3Folder::open(const int mode, bool failIfModeIsNotAvailable) -{ - if (!m_store) - throw exceptions::illegal_state("Store disconnected"); - - if (m_path.isEmpty()) - { - if (mode != MODE_READ_ONLY && failIfModeIsNotAvailable) - throw exceptions::operation_not_supported(); - - m_open = true; - m_mode = mode; - - m_messageCount = 0; - } - else if (m_path.getSize() == 1 && m_path[0].getBuffer() == "INBOX") - { - m_store->sendRequest("STAT"); - - string response; - m_store->readResponse(response, false); - - if (!m_store->isSuccessResponse(response)) - throw exceptions::command_error("STAT", response); - - m_store->stripResponseCode(response, response); - - std::istringstream iss(response); - iss >> m_messageCount; - - if (iss.fail()) - throw exceptions::invalid_response("STAT", response); - - m_open = true; - m_mode = mode; - } - else - { - throw exceptions::folder_not_found(); - } -} - -void POP3Folder::close(const bool expunge) -{ - if (!m_store) - throw exceptions::illegal_state("Store disconnected"); - - if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); - - if (!expunge) - { - m_store->sendRequest("RSET"); - - string response; - m_store->readResponse(response, false); - } - - m_open = false; - m_mode = -1; - - onClose(); -} - - -void POP3Folder::onClose() -{ - for (MessageMap::iterator it = m_messages.begin() ; it != m_messages.end() ; ++it) - (*it).first->onFolderClosed(); - - m_messages.clear(); -} - - -void POP3Folder::create(const int /* type */) -{ - throw exceptions::operation_not_supported(); -} - - -const bool POP3Folder::exists() -{ - if (!m_store) - throw exceptions::illegal_state("Store disconnected"); - - return (m_path.isEmpty() || (m_path.getSize() == 1 && m_path[0].getBuffer() == "INBOX")); -} - - -const bool POP3Folder::isOpen() const -{ - return (m_open); -} - - -message* POP3Folder::getMessage(const int num) -{ - if (!m_store) - throw exceptions::illegal_state("Store disconnected"); - else if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); - else if (num < 1 || num > m_messageCount) - throw exceptions::message_not_found(); - - return new POP3Message(this, num); -} - - -std::vector POP3Folder::getMessages(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 (to < from || from < 1 || to < 1 || from > m_messageCount || to > m_messageCount) - throw exceptions::message_not_found(); - - std::vector v; - - for (int i = from ; i <= to ; ++i) - v.push_back(new POP3Message(this, i)); - - return (v); -} - - -std::vector POP3Folder::getMessages(const std::vector & nums) -{ - if (!m_store) - throw exceptions::illegal_state("Store disconnected"); - else if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); - - std::vector v; - - try - { - for (std::vector ::const_iterator it = nums.begin() ; it != nums.end() ; ++it) - { - if (*it < 1|| *it > m_messageCount) - throw exceptions::message_not_found(); - - v.push_back(new POP3Message(this, *it)); - } - } - catch (std::exception& e) - { - for (std::vector ::iterator it = v.begin() ; it != v.end() ; ++it) - delete (*it); - - throw; - } - - return (v); -} - - -const int POP3Folder::getMessageCount() -{ - if (!m_store) - throw exceptions::illegal_state("Store disconnected"); - else if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); - - return (m_messageCount); -} - - -folder* POP3Folder::getFolder(const folder::path::component& name) -{ - if (!m_store) - throw exceptions::illegal_state("Store disconnected"); - - return new POP3Folder(m_path / name, m_store); -} - - -std::vector POP3Folder::getFolders(const bool /* recursive */) -{ - if (!m_store) - throw exceptions::illegal_state("Store disconnected"); - - if (m_path.isEmpty()) - { - std::vector v; - v.push_back(new POP3Folder(folder::path::component("INBOX"), m_store)); - return (v); - } - else - { - std::vector v; - return (v); - } -} - - -void POP3Folder::fetchMessages(std::vector & 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 ::iterator it = msg.begin() ; - it != msg.end() ; ++it) - { - dynamic_cast (*it)->fetch(this, options); - - if (progress) - progress->progress(++current, total); - } - - if (options & FETCH_SIZE) - { - // Send the "LIST" command - std::ostringstream command; - command << "LIST"; - - m_store->sendRequest(command.str()); - - // Get the response - string response; - m_store->readResponse(response, true, NULL); - - if (m_store->isSuccessResponse(response)) - { - m_store->stripFirstLine(response, response, NULL); - - // C: LIST - // S: +OK - // S: 1 47548 - // S: 2 12653 - // S: . - std::map result; - parseMultiListOrUidlResponse(response, result); - - for (std::vector ::iterator it = msg.begin() ; - it != msg.end() ; ++it) - { - POP3Message* m = dynamic_cast (*it); - - std::map ::const_iterator x = result.find(m->m_num); - - if (x != result.end()) - { - int size = 0; - - std::istringstream iss((*x).second); - iss >> size; - - m->m_size = size; - } - } - } - - } - - if (options & FETCH_UID) - { - // Send the "UIDL" command - std::ostringstream command; - command << "UIDL"; - - m_store->sendRequest(command.str()); - - // Get the response - string response; - m_store->readResponse(response, true, NULL); - - if (m_store->isSuccessResponse(response)) - { - m_store->stripFirstLine(response, response, NULL); - - // C: UIDL - // S: +OK - // S: 1 whqtswO00WBw418f9t5JxYwZ - // S: 2 QhdPYR:00WBw1Ph7x7 - // S: . - std::map result; - parseMultiListOrUidlResponse(response, result); - - for (std::vector ::iterator it = msg.begin() ; - it != msg.end() ; ++it) - { - POP3Message* m = dynamic_cast (*it); - - std::map ::const_iterator x = result.find(m->m_num); - - if (x != result.end()) - m->m_uid = (*x).second; - } - } - } - - if (progress) - progress->stop(total); -} - - -void POP3Folder::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 (msg)->fetch(this, options); - - if (options & FETCH_SIZE) - { - // Send the "LIST" command - std::ostringstream command; - command << "LIST " << msg->getNumber(); - - m_store->sendRequest(command.str()); - - // Get the response - string response; - m_store->readResponse(response, false, NULL); - - if (m_store->isSuccessResponse(response)) - { - m_store->stripResponseCode(response, response); - - // C: LIST 2 - // S: +OK 2 4242 - string::iterator it = response.begin(); - - while (it != response.end() && (*it == ' ' || *it == '\t')) ++it; - while (it != response.end() && !(*it == ' ' || *it == '\t')) ++it; - while (it != response.end() && (*it == ' ' || *it == '\t')) ++it; - - if (it != response.end()) - { - int size = 0; - - std::istringstream iss(string(it, response.end())); - iss >> size; - - dynamic_cast (msg)->m_size = size; - } - } - } - - if (options & FETCH_UID) - { - // Send the "UIDL" command - std::ostringstream command; - command << "UIDL " << msg->getNumber(); - - m_store->sendRequest(command.str()); - - // Get the response - string response; - m_store->readResponse(response, false, NULL); - - if (m_store->isSuccessResponse(response)) - { - m_store->stripResponseCode(response, response); - - // C: UIDL 2 - // S: +OK 2 QhdPYR:00WBw1Ph7x7 - string::iterator it = response.begin(); - - while (it != response.end() && (*it == ' ' || *it == '\t')) ++it; - while (it != response.end() && !(*it == ' ' || *it == '\t')) ++it; - while (it != response.end() && (*it == ' ' || *it == '\t')) ++it; - - if (it != response.end()) - { - dynamic_cast (msg)->m_uid = - string(it, response.end()); - } - } - } -} - - -const int POP3Folder::getFetchCapabilities() const -{ - return (FETCH_ENVELOPE | FETCH_CONTENT_INFO | - FETCH_SIZE | FETCH_FULL_HEADER | FETCH_UID); -} - - -folder* POP3Folder::getParent() -{ - return (m_path.isEmpty() ? NULL : new POP3Folder(m_path.getParent(), m_store)); -} - - -const store* POP3Folder::getStore() const -{ - return (m_store); -} - - -store* POP3Folder::getStore() -{ - return (m_store); -} - - -void POP3Folder::registerMessage(POP3Message* msg) -{ - m_messages.insert(MessageMap::value_type(msg, msg->getNumber())); -} - - -void POP3Folder::unregisterMessage(POP3Message* msg) -{ - m_messages.erase(msg); -} - - -void POP3Folder::onStoreDisconnected() -{ - m_store = NULL; -} - - -void POP3Folder::deleteMessage(const int num) -{ - if (!m_store) - throw exceptions::illegal_state("Store disconnected"); - else if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); - - std::ostringstream command; - command << "DELE " << num; - - m_store->sendRequest(command.str()); - - string response; - m_store->readResponse(response, false); - - if (!m_store->isSuccessResponse(response)) - throw exceptions::command_error("DELE", response); - - // Update local flags - for (std::map ::iterator it = - m_messages.begin() ; it != m_messages.end() ; ++it) - { - POP3Message* msg = (*it).first; - - if (msg->getNumber() == num) - msg->m_deleted = true; - } - - // Notify message flags changed - std::vector nums; - nums.push_back(num); - - events::messageChangedEvent event(this, events::messageChangedEvent::TYPE_FLAGS, nums); - - notifyMessageChanged(event); -} - - -void POP3Folder::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"); - - const int to2 = (to == -1 ? m_messageCount : to); - - for (int i = from ; i <= to2 ; ++i) - { - std::ostringstream command; - command << "DELE " << i; - - m_store->sendRequest(command.str()); - - string response; - m_store->readResponse(response, false); - - if (!m_store->isSuccessResponse(response)) - throw exceptions::command_error("DELE", response); - } - - // Update local flags - for (std::map ::iterator it = - m_messages.begin() ; it != m_messages.end() ; ++it) - { - POP3Message* msg = (*it).first; - - if (msg->getNumber() >= from && msg->getNumber() <= to2) - msg->m_deleted = true; - } - - // Notify message flags changed - std::vector nums; - - for (int i = from ; i <= to2 ; ++i) - nums.push_back(i); - - events::messageChangedEvent event(this, events::messageChangedEvent::TYPE_FLAGS, nums); - - notifyMessageChanged(event); -} - - -void POP3Folder::deleteMessages(const std::vector & 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"); - - for (std::vector ::const_iterator - it = nums.begin() ; it != nums.end() ; ++it) - { - std::ostringstream command; - command << "DELE " << (*it); - - m_store->sendRequest(command.str()); - - string response; - m_store->readResponse(response, false); - - if (!m_store->isSuccessResponse(response)) - throw exceptions::command_error("DELE", response); - } - - // Sort message list - std::vector list; - - list.resize(nums.size()); - std::copy(nums.begin(), nums.end(), list.begin()); - - std::sort(list.begin(), list.end()); - - // Update local flags - for (std::map ::iterator it = - m_messages.begin() ; it != m_messages.end() ; ++it) - { - POP3Message* msg = (*it).first; - - if (std::binary_search(list.begin(), list.end(), msg->getNumber())) - msg->m_deleted = true; - } - - // Notify message flags changed - events::messageChangedEvent event(this, events::messageChangedEvent::TYPE_FLAGS, list); - - notifyMessageChanged(event); -} - - -void POP3Folder::setMessageFlags(const int /* from */, const int /* to */, - const int /* flags */, const int /* mode */) -{ - throw exceptions::operation_not_supported(); -} - - -void POP3Folder::setMessageFlags(const std::vector & /* nums */, - const int /* flags */, const int /* mode */) -{ - throw exceptions::operation_not_supported(); -} - - -void POP3Folder::rename(const folder::path& /* newPath */) -{ - throw exceptions::operation_not_supported(); -} - - -void POP3Folder::addMessage(vmime::message* /* msg */, const int /* flags */, - vmime::datetime* /* date */, progressionListener* /* progress */) -{ - throw exceptions::operation_not_supported(); -} - - -void POP3Folder::addMessage(utility::inputStream& /* is */, const int /* size */, const int /* flags */, - vmime::datetime* /* date */, progressionListener* /* progress */) -{ - throw exceptions::operation_not_supported(); -} - - -void POP3Folder::copyMessage(const folder::path& /* dest */, const int /* num */) -{ - throw exceptions::operation_not_supported(); -} - - -void POP3Folder::copyMessages(const folder::path& /* dest */, const int /* from */, const int /* to */) -{ - throw exceptions::operation_not_supported(); -} - - -void POP3Folder::copyMessages(const folder::path& /* dest */, const std::vector & /* nums */) -{ - throw exceptions::operation_not_supported(); -} - - -void POP3Folder::status(int& count, int& unseen) -{ - if (!m_store) - throw exceptions::illegal_state("Store disconnected"); - else if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); - - m_store->sendRequest("STAT"); - - string response; - m_store->readResponse(response, false); - - if (!m_store->isSuccessResponse(response)) - throw exceptions::command_error("STAT", response); - - m_store->stripResponseCode(response, response); - - std::istringstream iss(response); - iss >> count; - - unseen = count; - - // Update local message count - if (m_messageCount != count) - { - const int oldCount = m_messageCount; - - m_messageCount = count; - - if (count > oldCount) - { - std::vector nums; - nums.reserve(count - oldCount); - - for (int i = oldCount + 1, j = 0 ; i <= count ; ++i, ++j) - nums[j] = i; - - // Notify message count changed - events::messageCountEvent event(this, events::messageCountEvent::TYPE_ADDED, nums); - - notifyMessageCount(event); - - // Notify folders with the same path - for (std::list ::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); - } - } - } - } -} - - -void POP3Folder::expunge() -{ - // Not supported by POP3 protocol (deleted messages are automatically - // expunged at the end of the session...). -} - - -void POP3Folder::parseMultiListOrUidlResponse(const string& response, std::map & result) -{ - std::istringstream iss(response); - std::map ids; - - string line; - - while (std::getline(iss, line)) - { - string::iterator it = line.begin(); - - while (it != line.end() && (*it == ' ' || *it == '\t')) - ++it; - - if (it != line.end()) - { - int number = 0; - - while (it != line.end() && (*it >= '0' && *it <= '9')) - { - number = (number * 10) + (*it - '0'); - ++it; - } - - while (it != line.end() && !(*it == ' ' || *it == '\t')) ++it; - while (it != line.end() && (*it == ' ' || *it == '\t')) ++it; - - if (it != line.end()) - { - result.insert(std::map ::value_type(number, string(it, line.end()))); - } - } - } -} - - -} // messaging -} // vmime diff --git a/src/messaging/POP3Message.cpp b/src/messaging/POP3Message.cpp deleted file mode 100644 index 3c84368c..00000000 --- a/src/messaging/POP3Message.cpp +++ /dev/null @@ -1,216 +0,0 @@ -// -// 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/POP3Message.hpp" -#include "vmime/messaging/POP3Folder.hpp" -#include "vmime/messaging/POP3Store.hpp" - -#include - - -namespace vmime { -namespace messaging { - - -POP3Message::POP3Message(POP3Folder* folder, const int num) - : m_folder(folder), m_num(num), m_size(-1), m_deleted(false), m_header(NULL) -{ - m_folder->registerMessage(this); -} - - -POP3Message::~POP3Message() -{ - if (m_folder) - m_folder->unregisterMessage(this); - - delete (m_header); -} - - -void POP3Message::onFolderClosed() -{ - m_folder = NULL; -} - - -const int POP3Message::getNumber() const -{ - return (m_num); -} - - -const message::uid POP3Message::getUniqueId() const -{ - return (m_uid); -} - - -const int POP3Message::getSize() const -{ - if (m_size == -1) - throw exceptions::unfetched_object(); - - return (m_size); -} - - -const bool POP3Message::isExpunged() const -{ - return (false); -} - - -const int POP3Message::getFlags() const -{ - int flags = FLAG_RECENT; - - if (m_deleted) - flags |= FLAG_DELETED; - - return (flags); -} - - -const structure& POP3Message::getStructure() const -{ - throw exceptions::operation_not_supported(); -} - - -structure& POP3Message::getStructure() -{ - throw exceptions::operation_not_supported(); -} - - -const header& POP3Message::getHeader() const -{ - if (m_header == NULL) - throw exceptions::unfetched_object(); - - return (*m_header); -} - - -void POP3Message::extract(utility::outputStream& os, progressionListener* progress, - const int start, const int length) const -{ - if (!m_folder) - throw exceptions::illegal_state("Folder closed"); - else if (!m_folder->m_store) - throw exceptions::illegal_state("Store disconnected"); - - if (start != 0 && length != -1) - throw exceptions::partial_fetch_not_supported(); - - // Emit the "RETR" command - std::ostringstream oss; - oss << "RETR " << m_num; - - const_cast (m_folder)->m_store->sendRequest(oss.str()); - - try - { - POP3Folder::MessageMap::const_iterator it = - m_folder->m_messages.find(const_cast (this)); - - const int totalSize = (it != m_folder->m_messages.end()) - ? (*it).second : 0; - - const_cast (m_folder)->m_store-> - readResponse(os, progress, totalSize); - } - catch (exceptions::command_error& e) - { - throw exceptions::command_error("RETR", e.response()); - } -} - - -void POP3Message::extractPart - (const part& /* p */, utility::outputStream& /* os */, progressionListener* /* progress */, - const int /* start */, const int /* length */) const -{ - throw exceptions::operation_not_supported(); -} - - -void POP3Message::fetchPartHeader(part& /* p */) -{ - throw exceptions::operation_not_supported(); -} - - -void POP3Message::fetch(POP3Folder* folder, const int options) -{ - if (m_folder != folder) - throw exceptions::folder_not_found(); - - // FETCH_STRUCTURE and FETCH_FLAGS are not supported by POP3. - if (options & (folder::FETCH_STRUCTURE | folder::FETCH_FLAGS)) - throw exceptions::operation_not_supported(); - - // Check for the real need to fetch the full header - if (!((options & folder::FETCH_ENVELOPE) || - (options & folder::FETCH_CONTENT_INFO) || - (options & folder::FETCH_FULL_HEADER))) - { - return; - } - - // No need to differenciate between FETCH_ENVELOPE, - // FETCH_CONTENT_INFO, ... since POP3 only permits to - // retrieve the whole header and not fields in particular. - - // Emit the "TOP" command - std::ostringstream oss; - oss << "TOP " << m_num << " 0"; - - m_folder->m_store->sendRequest(oss.str()); - - try - { - string buffer; - m_folder->m_store->readResponse(buffer, true); - - if (m_header != NULL) - { - delete (m_header); - m_header = NULL; - } - - m_header = new header(); - m_header->parse(buffer); - } - catch (exceptions::command_error& e) - { - throw exceptions::command_error("TOP", e.response()); - } -} - - -void POP3Message::setFlags(const int /* flags */, const int /* mode */) -{ - throw exceptions::operation_not_supported(); -} - - -} // messaging -} // vmime diff --git a/src/messaging/POP3Store.cpp b/src/messaging/POP3Store.cpp deleted file mode 100644 index 3129f14a..00000000 --- a/src/messaging/POP3Store.cpp +++ /dev/null @@ -1,628 +0,0 @@ -// -// 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/POP3Store.hpp" -#include "vmime/messaging/POP3Folder.hpp" - -#include "vmime/exception.hpp" -#include "vmime/platformDependant.hpp" -#include "vmime/messageId.hpp" -#include "vmime/utility/md5.hpp" - -#include - - -namespace vmime { -namespace messaging { - - -POP3Store::POP3Store(session* sess, authenticator* auth) - : store(sess, getInfosInstance(), auth), m_socket(NULL), - m_authentified(false), m_timeoutHandler(NULL) -{ -} - - -POP3Store::~POP3Store() -{ - if (isConnected()) - disconnect(); - else if (m_socket) - internalDisconnect(); -} - - -const string POP3Store::getProtocolName() const -{ - return "pop3"; -} - - -folder* POP3Store::getDefaultFolder() -{ - if (!isConnected()) - throw exceptions::illegal_state("Not connected"); - - return new POP3Folder(folder::path(folder::path::component("INBOX")), this); -} - - -folder* POP3Store::getRootFolder() -{ - if (!isConnected()) - throw exceptions::illegal_state("Not connected"); - - return new POP3Folder(folder::path(), this); -} - - -folder* POP3Store::getFolder(const folder::path& path) -{ - if (!isConnected()) - throw exceptions::illegal_state("Not connected"); - - return new POP3Folder(path, this); -} - - -const bool POP3Store::isValidFolderName(const folder::path::component& /* name */) const -{ - return true; -} - - -void POP3Store::connect() -{ - if (isConnected()) - throw exceptions::already_connected(); - - const string address = getSession()->getProperties()[sm_infos.getPropertyPrefix() + "server.address"]; - const port_t port = getSession()->getProperties().getProperty(sm_infos.getPropertyPrefix() + "server.port", sm_infos.getDefaultPort()); - - // Create the time-out handler - if (getSession()->getProperties().hasProperty - (sm_infos.getPropertyPrefix() + "timeout.factory")) - { - timeoutHandlerFactory* tof = platformDependant::getHandler()-> - getTimeoutHandlerFactory(getSession()->getProperties() - [sm_infos.getPropertyPrefix() + "timeout.factory"]); - - m_timeoutHandler = tof->create(); - } - - // Create and connect the socket - socketFactory* sf = platformDependant::getHandler()->getSocketFactory - (getSession()->getProperties().getProperty(sm_infos.getPropertyPrefix() + "server.socket-factory", string("default"))); - - m_socket = sf->create(); - m_socket->connect(address, port); - - // Connection - // - // eg: C: - // --- S: +OK MailSite POP3 Server 5.3.4.0 Ready <36938848.1056800841.634@somewhere.com> - - string response; - readResponse(response, false); - - if (isSuccessResponse(response)) - { - bool authentified = false; - - const authenticationInfos auth = getAuthenticator()->requestAuthInfos(); - - // Secured authentication with APOP (if requested and if available) - // - // eg: C: APOP vincent - // --- S: +OK vincent is a valid mailbox - messageId mid(response); - - if (getSession()->getProperties().getProperty(sm_infos.getPropertyPrefix() + "options.apop", false)) - { - if (mid.getLeft().length() && mid.getRight().length()) - { - // is the result of MD5 applied to "password" - sendRequest("APOP " + auth.getUsername() + " " - + utility::md5(mid.generate() + auth.getPassword()).hex()); - readResponse(response, false); - - if (isSuccessResponse(response)) - { - authentified = true; - } - else - { - if (getSession()->getProperties().getProperty(sm_infos.getPropertyPrefix() + - "options.apop.fallback", false) == false) - { - internalDisconnect(); - throw exceptions::authentication_error(response); - } - } - } - else - { - // APOP not supported - if (getSession()->getProperties().getProperty(sm_infos.getPropertyPrefix() + - "options.apop.fallback", false) == false) - { - // Can't fallback on basic authentification - internalDisconnect(); - throw exceptions::unsupported_option(); - } - } - } - - if (!authentified) - { - // Basic authentication - // - // eg: C: USER vincent - // --- S: +OK vincent is a valid mailbox - // - // C: PASS couic - // S: +OK vincent's maildrop has 2 messages (320 octets) - - sendRequest("USER " + auth.getUsername()); - readResponse(response, false); - - if (isSuccessResponse(response)) - { - sendRequest("PASS " + auth.getPassword()); - readResponse(response, false); - - if (!isSuccessResponse(response)) - { - internalDisconnect(); - throw exceptions::authentication_error(response); - } - } - else - { - internalDisconnect(); - throw exceptions::authentication_error(response); - } - } - } - else - { - internalDisconnect(); - throw exceptions::connection_greeting_error(response); - } - - m_authentified = true; -} - - -const bool POP3Store::isConnected() const -{ - return (m_socket && m_socket->isConnected() && m_authentified); -} - - -void POP3Store::disconnect() -{ - if (!isConnected()) - throw exceptions::not_connected(); - - internalDisconnect(); -} - - -void POP3Store::internalDisconnect() -{ - for (std::list ::iterator it = m_folders.begin() ; - it != m_folders.end() ; ++it) - { - (*it)->onStoreDisconnected(); - } - - m_folders.clear(); - - - sendRequest("QUIT"); - - m_socket->disconnect(); - - delete (m_socket); - m_socket = NULL; - - delete (m_timeoutHandler); - m_timeoutHandler = NULL; - - m_authentified = false; -} - - -void POP3Store::noop() -{ - m_socket->send("NOOP"); - - string response; - readResponse(response, false); - - if (!isSuccessResponse(response)) - throw exceptions::command_error("NOOP", response); -} - - -const bool POP3Store::isSuccessResponse(const string& buffer) -{ - static const string OK("+OK"); - - return (buffer.length() >= 3 && - std::equal(buffer.begin(), buffer.begin() + 3, OK.begin())); -} - - -const bool POP3Store::stripFirstLine(const string& buffer, string& result, string* firstLine) -{ - const string::size_type end = buffer.find('\n'); - - if (end != string::npos) - { - if (firstLine) *firstLine = buffer.substr(0, end); - result = buffer.substr(end + 1); - return (true); - } - else - { - result = buffer; - return (false); - } -} - - -void POP3Store::stripResponseCode(const string& buffer, string& result) -{ - const string::size_type pos = buffer.find_first_of(" \t"); - - if (pos != string::npos) - result = buffer.substr(pos + 1); - else - result = buffer; -} - - -void POP3Store::sendRequest(const string& buffer, const bool end) -{ - if (end) - m_socket->send(buffer + "\r\n"); - else - m_socket->send(buffer); -} - - -void POP3Store::readResponse(string& buffer, const bool multiLine, - progressionListener* progress) -{ - bool foundTerminator = false; - int current = 0, total = 0; - - if (progress) - progress->start(total); - - if (m_timeoutHandler) - m_timeoutHandler->resetTimeOut(); - - buffer.clear(); - - string::value_type last1 = '\0', last2 = '\0'; - - for ( ; !foundTerminator ; ) - { -#if 0 // not supported - // Check for possible cancellation - if (progress && progress->cancel()) - throw exceptions::operation_cancelled(); -#endif - - // Check whether the time-out delay is elapsed - if (m_timeoutHandler && m_timeoutHandler->isTimeOut()) - { - if (!m_timeoutHandler->handleTimeOut()) - throw exceptions::operation_timed_out(); - } - - // Receive data from the socket - string receiveBuffer; - m_socket->receive(receiveBuffer); - - if (receiveBuffer.empty()) // buffer is empty - { - platformDependant::getHandler()->wait(); - continue; - } - - // We have received data: reset the time-out counter - if (m_timeoutHandler) - m_timeoutHandler->resetTimeOut(); - - // Check for transparent characters: '\n..' becomes '\n.' - const string::value_type first = receiveBuffer[0]; - - if (first == '.' && last2 == '\n' && last1 == '.') - { - receiveBuffer.erase(receiveBuffer.begin()); - } - else if (receiveBuffer.length() >= 2 && first == '.' && - receiveBuffer[1] == '.' && last1 == '\n') - { - receiveBuffer.erase(receiveBuffer.begin()); - } - - for (string::size_type trans ; - string::npos != (trans = receiveBuffer.find("\n..")) ; ) - { - receiveBuffer.replace(trans, 3, "\n."); - } - - last1 = receiveBuffer[receiveBuffer.length() - 1]; - last2 = (receiveBuffer.length() >= 2) ? receiveBuffer[receiveBuffer.length() - 2] : 0; - - // Append the data to the response buffer - buffer += receiveBuffer; - current += receiveBuffer.length(); - - // Check for terminator string (and strip it if present) - foundTerminator = checkTerminator(buffer, multiLine); - - // Notify progression - if (progress) - { - total = std::max(total, current); - progress->progress(current, total); - } - - // If there is an error (-ERR) when executing a command that - // requires a multi-line response, the error response will - // include only one line, so we stop waiting for a multi-line - // terminator and check for a "normal" one. - if (multiLine && !foundTerminator && buffer.length() >= 4 && buffer[0] == '-') - { - foundTerminator = checkTerminator(buffer, false); - } - } - - if (progress) - progress->stop(total); -} - - -void POP3Store::readResponse(utility::outputStream& os, progressionListener* progress, - const int predictedSize) -{ - bool foundTerminator = false; - int current = 0, total = predictedSize; - - string temp; - bool codeDone = false; - - if (progress) - progress->start(total); - - if (m_timeoutHandler) - m_timeoutHandler->resetTimeOut(); - - string::value_type last1 = '\0', last2 = '\0'; - - for ( ; !foundTerminator ; ) - { -#if 0 // not supported - // Check for possible cancellation - if (progress && progress->cancel()) - throw exceptions::operation_cancelled(); -#endif - - // Check whether the time-out delay is elapsed - if (m_timeoutHandler && m_timeoutHandler->isTimeOut()) - { - if (!m_timeoutHandler->handleTimeOut()) - throw exceptions::operation_timed_out(); - } - - // Receive data from the socket - string receiveBuffer; - m_socket->receive(receiveBuffer); - - if (receiveBuffer.empty()) // buffer is empty - { - platformDependant::getHandler()->wait(); - continue; - } - - // We have received data: reset the time-out counter - if (m_timeoutHandler) - m_timeoutHandler->resetTimeOut(); - - // Check for transparent characters: '\n..' becomes '\n.' - const string::value_type first = receiveBuffer[0]; - - if (first == '.' && last2 == '\n' && last1 == '.') - { - receiveBuffer.erase(receiveBuffer.begin()); - } - else if (receiveBuffer.length() >= 2 && first == '.' && - receiveBuffer[1] == '.' && last1 == '\n') - { - receiveBuffer.erase(receiveBuffer.begin()); - } - - for (string::size_type trans ; - string::npos != (trans = receiveBuffer.find("\n..")) ; ) - { - receiveBuffer.replace(trans, 3, "\n."); - } - - last1 = receiveBuffer[receiveBuffer.length() - 1]; - last2 = (receiveBuffer.length() >= 2) ? receiveBuffer[receiveBuffer.length() - 2] : 0; - - // If we don't have extracted the response code yet - if (!codeDone) - { - temp += receiveBuffer; - - string firstLine; - - if (stripFirstLine(temp, temp, &firstLine) == true) - { - if (!isSuccessResponse(firstLine)) - throw exceptions::command_error("?", firstLine); - - receiveBuffer = temp; - temp.clear(); - - codeDone = true; - } - } - - if (codeDone) - { - // Check for terminator string (and strip it if present) - foundTerminator = checkTerminator(receiveBuffer, true); - - // Inject the data into the output stream - os.write(receiveBuffer.data(), receiveBuffer.length()); - current += receiveBuffer.length(); - - // Notify progression - if (progress) - { - total = std::max(total, current); - progress->progress(current, total); - } - } - } - - if (progress) - progress->stop(total); -} - - -const bool POP3Store::checkTerminator(string& buffer, const bool multiLine) -{ - // Multi-line response - if (multiLine) - { - static const string term1("\r\n.\r\n"); - static const string term2("\n.\n"); - - return (checkOneTerminator(buffer, term1) || - checkOneTerminator(buffer, term2)); - } - // Normal response - else - { - static const string term1("\r\n"); - static const string term2("\n"); - - return (checkOneTerminator(buffer, term1) || - checkOneTerminator(buffer, term2)); - } - - return (false); -} - - -const bool POP3Store::checkOneTerminator(string& buffer, const string& term) -{ - if (buffer.length() >= term.length() && - std::equal(buffer.end() - term.length(), buffer.end(), term.begin())) - { - buffer.erase(buffer.end() - term.length(), buffer.end()); - return (true); - } - - return (false); -} - - -void POP3Store::registerFolder(POP3Folder* folder) -{ - m_folders.push_back(folder); -} - - -void POP3Store::unregisterFolder(POP3Folder* folder) -{ - std::list ::iterator it = std::find(m_folders.begin(), m_folders.end(), folder); - if (it != m_folders.end()) m_folders.erase(it); -} - - -const int POP3Store::getCapabilities() const -{ - return (CAPABILITY_DELETE_MESSAGE); -} - - - -// Service infos - -POP3Store::_infos POP3Store::sm_infos; - - -const serviceInfos& POP3Store::getInfosInstance() -{ - return (sm_infos); -} - - -const serviceInfos& POP3Store::getInfos() const -{ - return (sm_infos); -} - - -const port_t POP3Store::_infos::getDefaultPort() const -{ - return (110); -} - - -const string POP3Store::_infos::getPropertyPrefix() const -{ - return "store.pop3."; -} - - -const std::vector POP3Store::_infos::getAvailableProperties() const -{ - std::vector list; - - // POP3-specific options - list.push_back("options.apop"); - list.push_back("options.apop.fallback"); - - // Common properties - list.push_back("auth.username"); - list.push_back("auth.password"); - - list.push_back("server.address"); - list.push_back("server.port"); - list.push_back("server.socket-factory"); - - list.push_back("timeout.factory"); - - return (list); -} - - -} // messaging -} // vmime diff --git a/src/messaging/SMTPTransport.cpp b/src/messaging/SMTPTransport.cpp deleted file mode 100644 index bd10fbe0..00000000 --- a/src/messaging/SMTPTransport.cpp +++ /dev/null @@ -1,591 +0,0 @@ -// -// 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/SMTPTransport.hpp" - -#include "vmime/exception.hpp" -#include "vmime/platformDependant.hpp" -#include "vmime/encoderB64.hpp" -#include "vmime/message.hpp" -#include "vmime/mailboxList.hpp" - -#include "vmime/messaging/authHelper.hpp" - - -namespace vmime { -namespace messaging { - - -SMTPTransport::SMTPTransport(session* sess, authenticator* auth) - : transport(sess, getInfosInstance(), auth), m_socket(NULL), - m_authentified(false), m_extendedSMTP(false), m_timeoutHandler(NULL) -{ -} - - -SMTPTransport::~SMTPTransport() -{ - if (isConnected()) - disconnect(); - else if (m_socket) - internalDisconnect(); -} - - -const string SMTPTransport::getProtocolName() const -{ - return "smtp"; -} - - -void SMTPTransport::connect() -{ - if (isConnected()) - throw exceptions::already_connected(); - - const string address = getSession()->getProperties()[sm_infos.getPropertyPrefix() + "server.address"]; - const port_t port = getSession()->getProperties().getProperty(sm_infos.getPropertyPrefix() + "server.port", sm_infos.getDefaultPort()); - - // Create the time-out handler - if (getSession()->getProperties().hasProperty - (sm_infos.getPropertyPrefix() + "timeout.factory")) - { - timeoutHandlerFactory* tof = platformDependant::getHandler()-> - getTimeoutHandlerFactory(getSession()->getProperties() - [sm_infos.getPropertyPrefix() + "timeout.factory"]); - - m_timeoutHandler = tof->create(); - } - - // Create and connect the socket - socketFactory* sf = platformDependant::getHandler()->getSocketFactory - (getSession()->getProperties().getProperty(sm_infos.getPropertyPrefix() + "server.socket-factory", string("default"))); - - m_socket = sf->create(); - m_socket->connect(address, port); - - // Connection - // - // eg: C: - // --- S: 220 smtp.domain.com Service ready - - string response; - readResponse(response); - - if (responseCode(response) != 220) - { - internalDisconnect(); - throw exceptions::connection_greeting_error(response); - } - - // Identification - // First, try Extended SMTP (ESMTP) - // - // eg: C: EHLO thismachine.ourdomain.com - // S: 250 OK - - sendRequest("EHLO " + platformDependant::getHandler()->getHostName()); - readResponse(response); - - if (responseCode(response) != 250) - { - // Next, try "Basic" SMTP - // - // eg: C: HELO thismachine.ourdomain.com - // S: 250 OK - - sendRequest("HELO " + platformDependant::getHandler()->getHostName()); - readResponse(response); - - if (responseCode(response) != 250) - { - internalDisconnect(); - throw exceptions::connection_greeting_error(response); - } - - m_extendedSMTP = false; - } - else - { - m_extendedSMTP = true; - } - - // Authentication - if (getSession()->getProperties().getProperty - (sm_infos.getPropertyPrefix() + "options.need-authentication", false) == true) - { - if (!m_extendedSMTP) - { - internalDisconnect(); - throw exceptions::command_error("AUTH", "ESMTP not supported."); - } - - const authenticationInfos auth = getAuthenticator()->requestAuthInfos(); - bool authentified = false; - - enum AuthMethods - { - First = 0, - CRAM_MD5 = First, - // TODO: more authentication methods... - End - }; - - for (int currentMethod = First ; !authentified ; ++currentMethod) - { - switch (currentMethod) - { - case CRAM_MD5: - { - sendRequest("AUTH CRAM-MD5"); - readResponse(response); - - if (responseCode(response) == 334) - { - encoderB64 base64; - - string challengeB64 = responseText(response); - string challenge, challengeHex; - - { - utility::inputStreamStringAdapter in(challengeB64); - utility::outputStreamStringAdapter out(challenge); - - base64.decode(in, out); - } - - hmac_md5(challenge, auth.getPassword(), challengeHex); - - string decoded = auth.getUsername() + " " + challengeHex; - string encoded; - - { - utility::inputStreamStringAdapter in(decoded); - utility::outputStreamStringAdapter out(encoded); - - base64.encode(in, out); - } - - sendRequest(encoded); - readResponse(response); - - if (responseCode(response) == 235) - { - authentified = true; - } - else - { - internalDisconnect(); - throw exceptions::authentication_error(response); - } - } - - break; - } - case End: - { - // All authentication methods have been tried and - // the server does not understand any. - throw exceptions::authentication_error(response); - } - - } - } - } - - m_authentified = true; -} - - -const bool SMTPTransport::isConnected() const -{ - return (m_socket && m_socket->isConnected() && m_authentified); -} - - -void SMTPTransport::disconnect() -{ - if (!isConnected()) - throw exceptions::not_connected(); - - internalDisconnect(); -} - - -void SMTPTransport::internalDisconnect() -{ - sendRequest("QUIT"); - - m_socket->disconnect(); - - delete (m_socket); - m_socket = NULL; - - delete (m_timeoutHandler); - m_timeoutHandler = NULL; - - m_authentified = false; - m_extendedSMTP = false; -} - - -void SMTPTransport::noop() -{ - m_socket->send("NOOP"); - - string response; - readResponse(response); - - if (responseCode(response) != 250) - throw exceptions::command_error("NOOP", response); -} - - -static void extractMailboxes - (mailboxList& recipients, const addressList& list) -{ - for (int i = 0 ; i < list.getAddressCount() ; ++i) - { - mailbox* mbox = dynamic_cast (list.getAddressAt(i)->clone()); - - if (mbox != NULL) - recipients.appendMailbox(mbox); - } -} - - -void SMTPTransport::send(vmime::message* msg, progressionListener* progress) -{ - // Extract expeditor - mailbox expeditor; - - try - { - const mailboxField& from = dynamic_cast - (*msg->getHeader()->findField(fields::FROM)); - expeditor = from.getValue(); - } - catch (exceptions::no_such_field&) - { - throw exceptions::no_expeditor(); - } - - // Extract recipients - mailboxList recipients; - - try - { - const addressListField& to = dynamic_cast - (*msg->getHeader()->findField(fields::TO)); - extractMailboxes(recipients, to.getValue()); - } - catch (exceptions::no_such_field&) { } - - try - { - const addressListField& cc = dynamic_cast - (*msg->getHeader()->findField(fields::CC)); - extractMailboxes(recipients, cc.getValue()); - } - catch (exceptions::no_such_field&) { } - - try - { - const addressListField& bcc = dynamic_cast - (*msg->getHeader()->findField(fields::BCC)); - extractMailboxes(recipients, bcc.getValue()); - } - catch (exceptions::no_such_field&) { } - - // Generate the message, "stream" it and delegate the sending - // to the generic send() function. - std::ostringstream oss; - utility::outputStreamAdapter ossAdapter(oss); - - msg->generate(ossAdapter); - - const string& str(oss.str()); - - utility::inputStreamStringAdapter isAdapter(str); - - send(expeditor, recipients, isAdapter, str.length(), progress); -} - - -void SMTPTransport::send(const mailbox& expeditor, const mailboxList& recipients, - utility::inputStream& is, const utility::stream::size_type size, - progressionListener* progress) -{ - // If no recipient/expeditor was found, throw an exception - if (recipients.isEmpty()) - throw exceptions::no_recipient(); - else if (expeditor.isEmpty()) - throw exceptions::no_expeditor(); - - // Emit the "MAIL" command - string response; - - sendRequest("MAIL FROM: <" + expeditor.getEmail() + ">"); - readResponse(response); - - if (responseCode(response) != 250) - { - internalDisconnect(); - throw exceptions::command_error("MAIL", response); - } - - // Emit a "RCPT TO" command for each recipient - for (int i = 0 ; i < recipients.getMailboxCount() ; ++i) - { - const mailbox& mbox = *recipients.getMailboxAt(i); - - sendRequest("RCPT TO: <" + mbox.getEmail() + ">"); - readResponse(response); - - if (responseCode(response) != 250) - { - internalDisconnect(); - throw exceptions::command_error("RCPT TO", response); - } - } - - // Send the message data - sendRequest("DATA"); - readResponse(response); - - if (responseCode(response) != 354) - { - internalDisconnect(); - throw exceptions::command_error("DATA", response); - } - - int current = 0, total = size; - - if (progress) - progress->start(total); - - char buffer[65536]; - - while (!is.eof()) - { - const int read = is.read(buffer, sizeof(buffer)); - - // Transform '.' into '..' at the beginning of a line - char* start = buffer; - char* end = buffer + read; - char* pos = buffer; - - while ((pos = std::find(pos, end, '.')) != end) - { - if (pos > buffer && *(pos - 1) == '\n') - { - m_socket->sendRaw(start, pos - start); - m_socket->sendRaw(".", 1); - - start = pos; - } - - ++pos; - } - - // Send the remaining data - m_socket->sendRaw(start, end - start); - - current += read; - - // Notify progression - if (progress) - { - total = std::max(total, current); - progress->progress(current, total); - } - } - - if (progress) - progress->stop(total); - - m_socket->sendRaw("\r\n.\r\n", 5); - readResponse(response); - - if (responseCode(response) != 250) - { - internalDisconnect(); - throw exceptions::command_error("DATA", response); - } -} - - -void SMTPTransport::sendRequest(const string& buffer, const bool end) -{ - m_socket->send(buffer); - if (end) m_socket->send("\r\n"); -} - - -const int SMTPTransport::responseCode(const string& response) -{ - int code = 0; - - if (response.length() >= 3) - { - code = (response[0] - '0') * 100 - + (response[1] - '0') * 10 - + (response[2] - '0'); - } - - return (code); -} - - -const string SMTPTransport::responseText(const string& response) -{ - string text; - - std::istringstream iss(response); - std::string line; - - while (std::getline(iss, line)) - { - if (line.length() >= 4) - text += line.substr(4); - else - text += line; - - text += "\n"; - } - - return (text); -} - - -void SMTPTransport::readResponse(string& buffer) -{ - bool foundTerminator = false; - - buffer.clear(); - - for ( ; !foundTerminator ; ) - { - // Check whether the time-out delay is elapsed - if (m_timeoutHandler && m_timeoutHandler->isTimeOut()) - { - if (!m_timeoutHandler->handleTimeOut()) - throw exceptions::operation_timed_out(); - } - - // Receive data from the socket - string receiveBuffer; - m_socket->receive(receiveBuffer); - - if (receiveBuffer.empty()) // buffer is empty - { - platformDependant::getHandler()->wait(); - continue; - } - - // We have received data: reset the time-out counter - if (m_timeoutHandler) - m_timeoutHandler->resetTimeOut(); - - // Append the data to the response buffer - buffer += receiveBuffer; - - // Check for terminator string (and strip it if present) - if (buffer.length() >= 2 && buffer[buffer.length() - 1] == '\n') - { - string::size_type p = buffer.length() - 2; - bool end = false; - - for ( ; !end ; --p) - { - if (p == 0 || buffer[p] == '\n') - { - end = true; - - if (p + 4 < buffer.length()) - foundTerminator = true; - } - } - } - } - - // Remove [CR]LF at the end of the response - if (buffer.length() >= 2 && buffer[buffer.length() - 1] == '\n') - { - if (buffer[buffer.length() - 2] == '\r') - buffer.resize(buffer.length() - 2); - else - buffer.resize(buffer.length() - 1); - } -} - - - -// Service infos - -SMTPTransport::_infos SMTPTransport::sm_infos; - - -const serviceInfos& SMTPTransport::getInfosInstance() -{ - return (sm_infos); -} - - -const serviceInfos& SMTPTransport::getInfos() const -{ - return (sm_infos); -} - - -const port_t SMTPTransport::_infos::getDefaultPort() const -{ - return (25); -} - - -const string SMTPTransport::_infos::getPropertyPrefix() const -{ - return "transport.smtp."; -} - - -const std::vector SMTPTransport::_infos::getAvailableProperties() const -{ - std::vector list; - - // SMTP-specific options - list.push_back("options.need-authentication"); - - // Common properties - list.push_back("auth.username"); - list.push_back("auth.password"); - - list.push_back("server.address"); - list.push_back("server.port"); - list.push_back("server.socket-factory"); - - list.push_back("timeout.factory"); - - return (list); -} - - -} // messaging -} // vmime diff --git a/src/messaging/builtinServices.inl b/src/messaging/builtinServices.inl index d98e7949..cea2f7a8 100644 --- a/src/messaging/builtinServices.inl +++ b/src/messaging/builtinServices.inl @@ -25,26 +25,26 @@ #if VMIME_BUILTIN_MESSAGING_PROTO_POP3 - #include "vmime/messaging/POP3Store.hpp" - REGISTER_SERVICE(POP3Store, pop3); + #include "vmime/messaging/pop3/POP3Store.hpp" + REGISTER_SERVICE(pop3::POP3Store, pop3); #endif #if VMIME_BUILTIN_MESSAGING_PROTO_SMTP - #include "vmime/messaging/SMTPTransport.hpp" - REGISTER_SERVICE(SMTPTransport, smtp); + #include "vmime/messaging/smtp/SMTPTransport.hpp" + REGISTER_SERVICE(smtp::SMTPTransport, smtp); #endif #if VMIME_BUILTIN_MESSAGING_PROTO_IMAP - #include "vmime/messaging/IMAPStore.hpp" - REGISTER_SERVICE(IMAPStore, imap); + #include "vmime/messaging/imap/IMAPStore.hpp" + REGISTER_SERVICE(imap::IMAPStore, imap); #endif #if VMIME_BUILTIN_MESSAGING_PROTO_MAILDIR - #include "vmime/messaging/maildirStore.hpp" - REGISTER_SERVICE(maildirStore, maildir); + #include "vmime/messaging/maildir/maildirStore.hpp" + REGISTER_SERVICE(maildir::maildirStore, maildir); #endif diff --git a/src/messaging/imap/IMAPConnection.cpp b/src/messaging/imap/IMAPConnection.cpp new file mode 100644 index 00000000..0f5934c8 --- /dev/null +++ b/src/messaging/imap/IMAPConnection.cpp @@ -0,0 +1,265 @@ +// +// 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/imap/IMAPTag.hpp" +#include "vmime/messaging/imap/IMAPConnection.hpp" +#include "vmime/messaging/imap/IMAPUtils.hpp" +#include "vmime/messaging/imap/IMAPStore.hpp" + +#include "vmime/exception.hpp" +#include "vmime/platformDependant.hpp" + +#include + + +namespace vmime { +namespace messaging { +namespace imap { + + +IMAPConnection::IMAPConnection(IMAPStore* store, authenticator* auth) + : m_store(store), m_auth(auth), m_socket(NULL), m_parser(NULL), m_tag(NULL), + m_hierarchySeparator('\0'), m_state(STATE_NONE), m_timeoutHandler(NULL) +{ +} + + +IMAPConnection::~IMAPConnection() +{ + if (isConnected()) + disconnect(); + else if (m_socket) + internalDisconnect(); + + delete (m_tag); + delete (m_parser); +} + + +void IMAPConnection::connect() +{ + if (isConnected()) + throw exceptions::already_connected(); + + m_state = STATE_NONE; + m_hierarchySeparator = '\0'; + + const string address = m_store->getSession()->getProperties()[m_store->getInfos().getPropertyPrefix() + "server.address"]; + const port_t port = m_store->getSession()->getProperties().getProperty(m_store->getInfos().getPropertyPrefix() + "server.port", m_store->getInfos().getDefaultPort()); + + // Create the time-out handler + if (m_store->getSession()->getProperties().hasProperty + (m_store->getInfos().getPropertyPrefix() + "timeout.factory")) + { + timeoutHandlerFactory* tof = platformDependant::getHandler()-> + getTimeoutHandlerFactory(m_store->getSession()->getProperties() + [m_store->getInfos().getPropertyPrefix() + "timeout.factory"]); + + m_timeoutHandler = tof->create(); + } + + // Create and connect the socket + socketFactory* sf = platformDependant::getHandler()->getSocketFactory + (m_store->getSession()->getProperties().getProperty + (m_store->getInfos().getPropertyPrefix() + "server.socket-factory", string("default"))); + + m_socket = sf->create(); + m_socket->connect(address, port); + + delete (m_tag); + m_tag = new IMAPTag(); + + delete (m_parser); + m_parser = new IMAPParser(m_tag, m_socket, m_timeoutHandler); + + + setState(STATE_NON_AUTHENTICATED); + + + // Connection greeting + // + // eg: C: + // --- S: * OK mydomain.org IMAP4rev1 v12.256 server ready + + utility::auto_ptr greet(m_parser->readGreeting()); + + if (greet->resp_cond_bye()) + { + internalDisconnect(); + throw exceptions::connection_greeting_error(m_parser->lastLine()); + } + else if (greet->resp_cond_auth()->condition() != IMAPParser::resp_cond_auth::PREAUTH) + { + const authenticationInfos auth = m_auth->requestAuthInfos(); + + // TODO: other authentication methods + + send(true, "LOGIN " + IMAPUtils::quoteString(auth.getUsername()) + + " " + IMAPUtils::quoteString(auth.getPassword()), true); + + utility::auto_ptr resp(m_parser->readResponse()); + + if (resp->isBad()) + { + internalDisconnect(); + throw exceptions::command_error("LOGIN", m_parser->lastLine()); + } + else if (resp->response_done()->response_tagged()-> + resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) + { + internalDisconnect(); + throw exceptions::authentication_error(m_parser->lastLine()); + } + } + + // Get the hierarchy separator character + initHierarchySeparator(); + + // Switch to state "Authenticated" + setState(STATE_AUTHENTICATED); +} + + +const bool IMAPConnection::isConnected() const +{ + return (m_socket && m_socket->isConnected() && + (m_state == STATE_AUTHENTICATED || m_state == STATE_SELECTED)); +} + + +void IMAPConnection::disconnect() +{ + if (!isConnected()) + throw exceptions::not_connected(); + + internalDisconnect(); +} + + +void IMAPConnection::internalDisconnect() +{ + send(true, "LOGOUT", true); + + m_socket->disconnect(); + + delete (m_socket); + m_socket = NULL; + + delete (m_timeoutHandler); + m_timeoutHandler = NULL; + + m_state = STATE_LOGOUT; +} + + +void IMAPConnection::initHierarchySeparator() +{ + send(true, "LIST \"\" \"\"", true); + + utility::auto_ptr resp(m_parser->readResponse()); + + if (resp->isBad() || resp->response_done()->response_tagged()-> + resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) + { + internalDisconnect(); + throw exceptions::command_error("LIST", m_parser->lastLine(), "bad response"); + } + + const std::vector & respDataList = + resp->continue_req_or_response_data(); + + if (respDataList.size() < 1 || respDataList[0]->response_data() == NULL) + { + internalDisconnect(); + throw exceptions::command_error("LIST", m_parser->lastLine(), "unexpected response"); + } + + const IMAPParser::mailbox_data* mailboxData = + static_cast (respDataList[0]->response_data())-> + mailbox_data(); + + if (mailboxData == NULL || mailboxData->type() != IMAPParser::mailbox_data::LIST) + { + internalDisconnect(); + throw exceptions::command_error("LIST", m_parser->lastLine(), "invalid type"); + } + + if (mailboxData->mailbox_list()->quoted_char() == '\0') + { + internalDisconnect(); + throw exceptions::command_error("LIST", m_parser->lastLine(), "no hierarchy separator"); + } + + m_hierarchySeparator = mailboxData->mailbox_list()->quoted_char(); +} + + +void IMAPConnection::send(bool tag, const string& what, bool end) +{ +#if VMIME_DEBUG + std::ostringstream oss; + + if (tag) + { + ++(*m_tag); + + oss << string(*m_tag); + oss << " "; + } + + oss << what; + + if (end) + oss << "\r\n"; + + m_socket->send(oss.str()); +#else + if (tag) + { + ++(*m_tag); + + m_socket->send(*m_tag); + m_socket->send(" "); + } + + m_socket->send(what); + + if (end) + { + m_socket->send("\r\n"); + } +#endif +} + + +void IMAPConnection::sendRaw(const char* buffer, const int count) +{ + m_socket->sendRaw(buffer, count); +} + + +IMAPParser::response* IMAPConnection::readResponse(IMAPParser::literalHandler* lh) +{ + return (m_parser->readResponse(lh)); +} + + +} // imap +} // messaging +} // vmime 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 +// +// 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 +#include + + +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 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 & 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("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 ::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 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 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 & 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("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 IMAPFolder::getMessages(const int from, const int to) +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + std::vector v; + + for (int i = from ; i <= to ; ++i) + v.push_back(new IMAPMessage(this, i)); + + return (v); +} + + +std::vector IMAPFolder::getMessages(const std::vector & nums) +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + std::vector v; + + for (std::vector ::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 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 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 & respDataList = + resp->continue_req_or_response_data(); + + + std::vector v; + + try + { + for (std::vector ::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 ::iterator it = v.begin() ; it != v.end() ; ++it) + delete (*it); + + throw; + } + + return (v); +} + + +void IMAPFolder::fetchMessages(std::vector & 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 ::iterator it = msg.begin() ; + it != msg.end() ; ++it) + { + dynamic_cast (*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 (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 ::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 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 ::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 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 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 ::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 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 & 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 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 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 ::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 ::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 ::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 ::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 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 & 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 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 ::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 ::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 ::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 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 resp(m_connection->readResponse()); + + bool ok = false; + const std::vector & respList + = resp->continue_req_or_response_data(); + + for (std::vector ::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 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 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 ::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 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 & respDataList = + resp->continue_req_or_response_data(); + + std::vector nums; + + for (std::vector ::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 ::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 ::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 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 ::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 nums; + nums.push_back(num); + + events::messageCountEvent event(this, events::messageCountEvent::TYPE_ADDED, nums); + + for (std::list ::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 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 ::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 & 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 ::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 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 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 & 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("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 & statusList = + responseData->mailbox_data()->status_info_list(); + + for (std::vector ::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 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 ::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 diff --git a/src/messaging/imap/IMAPMessage.cpp b/src/messaging/imap/IMAPMessage.cpp new file mode 100644 index 00000000..629668c5 --- /dev/null +++ b/src/messaging/imap/IMAPMessage.cpp @@ -0,0 +1,843 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/messaging/imap/IMAPParser.hpp" +#include "vmime/messaging/imap/IMAPMessage.hpp" +#include "vmime/messaging/imap/IMAPFolder.hpp" +#include "vmime/messaging/imap/IMAPStore.hpp" +#include "vmime/messaging/imap/IMAPConnection.hpp" +#include "vmime/messaging/imap/IMAPUtils.hpp" + +#include +#include + + +namespace vmime { +namespace messaging { +namespace imap { + + +// +// IMAPpart +// + +class IMAPstructure; + +class IMAPpart : public part +{ +private: + + IMAPpart(IMAPpart* parent, const int number, const IMAPParser::body_type_mpart* mpart); + IMAPpart(IMAPpart* parent, const int number, const IMAPParser::body_type_1part* part); + +public: + + ~IMAPpart(); + + const structure& getStructure() const; + structure& getStructure(); + + const IMAPpart* getParent() const { return (m_parent); } + + const mediaType& getType() const { return (m_mediaType); } + const int getSize() const { return (m_size); } + const int getNumber() const { return (m_number); } + + const header& getHeader() const + { + if (m_header == NULL) + throw exceptions::unfetched_object(); + else + return (*m_header); + } + + + static IMAPpart* create(IMAPpart* parent, const int number, const IMAPParser::body* body) + { + if (body->body_type_mpart()) + return new IMAPpart(parent, number, body->body_type_mpart()); + else + return new IMAPpart(parent, number, body->body_type_1part()); + } + + + header& getOrCreateHeader() + { + if (m_header != NULL) + return (*m_header); + else + return (*(m_header = new header())); + } + +private: + + IMAPstructure* m_structure; + IMAPpart* m_parent; + header* m_header; + + int m_number; + int m_size; + mediaType m_mediaType; +}; + + + +// +// IMAPstructure +// + +class IMAPstructure : public structure +{ +private: + + IMAPstructure() + { + } + +public: + + IMAPstructure(const IMAPParser::body* body) + { + m_parts.push_back(IMAPpart::create(NULL, 1, body)); + } + + IMAPstructure(IMAPpart* parent, const std::vector & 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); + } + } +} + + +} // imap +} // messaging +} // vmime diff --git a/src/messaging/imap/IMAPStore.cpp b/src/messaging/imap/IMAPStore.cpp new file mode 100644 index 00000000..81eab9a7 --- /dev/null +++ b/src/messaging/imap/IMAPStore.cpp @@ -0,0 +1,294 @@ +// +// 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/imap/IMAPStore.hpp" +#include "vmime/messaging/imap/IMAPFolder.hpp" +#include "vmime/messaging/imap/IMAPConnection.hpp" + +#include "vmime/exception.hpp" +#include "vmime/platformDependant.hpp" + +#include + + +namespace vmime { +namespace messaging { +namespace imap { + + +#ifndef VMIME_BUILDING_DOC + +// +// IMAPauthenticator: private class used internally +// +// Used to request user credentials only in the first authentication +// and reuse this information the next times +// + +class IMAPauthenticator : public authenticator +{ +public: + + IMAPauthenticator(authenticator* auth) + : m_auth(auth), m_infos(NULL) + { + } + + ~IMAPauthenticator() + { + delete (m_infos); + } + + const authenticationInfos requestAuthInfos() const + { + if (m_infos == NULL) + m_infos = new authenticationInfos(m_auth->requestAuthInfos()); + + return (*m_infos); + } + +private: + + authenticator* m_auth; + mutable authenticationInfos* m_infos; +}; + +#endif // VMIME_BUILDING_DOC + + + +// +// IMAPStore +// + +IMAPStore::IMAPStore(session* sess, authenticator* auth) + : store(sess, getInfosInstance(), auth), + m_connection(NULL), m_oneTimeAuth(NULL) +{ +} + + +IMAPStore::~IMAPStore() +{ + if (isConnected()) + disconnect(); +} + + +authenticator* IMAPStore::oneTimeAuthenticator() +{ + return (m_oneTimeAuth); +} + + +const string IMAPStore::getProtocolName() const +{ + return "imap"; +} + + +folder* IMAPStore::getRootFolder() +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return new IMAPFolder(folder::path(), this); +} + + +folder* IMAPStore::getDefaultFolder() +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return new IMAPFolder(folder::path::component("INBOX"), this); +} + + +folder* IMAPStore::getFolder(const folder::path& path) +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return new IMAPFolder(path, this); +} + + +const bool IMAPStore::isValidFolderName(const folder::path::component& /* name */) const +{ + return true; +} + + +void IMAPStore::connect() +{ + if (isConnected()) + throw exceptions::already_connected(); + + m_oneTimeAuth = new IMAPauthenticator(getAuthenticator()); + + m_connection = new IMAPConnection(this, m_oneTimeAuth); + + try + { + m_connection->connect(); + } + catch (std::exception&) + { + delete (m_connection); + m_connection = NULL; + throw; + } +} + + +const bool IMAPStore::isConnected() const +{ + return (m_connection && m_connection->isConnected()); +} + + +void IMAPStore::disconnect() +{ + if (!isConnected()) + throw exceptions::not_connected(); + + for (std::list ::iterator it = m_folders.begin() ; + it != m_folders.end() ; ++it) + { + (*it)->onStoreDisconnected(); + } + + m_folders.clear(); + + + m_connection->disconnect(); + + delete (m_oneTimeAuth); + m_oneTimeAuth = NULL; + + delete (m_connection); + m_connection = NULL; +} + + +void IMAPStore::noop() +{ + if (!isConnected()) + throw exceptions::not_connected(); + + m_connection->send(true, "NOOP", true); + + utility::auto_ptr 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", m_connection->getParser()->lastLine()); + } +} + + +IMAPConnection* IMAPStore::connection() +{ + return (m_connection); +} + + +void IMAPStore::registerFolder(IMAPFolder* folder) +{ + m_folders.push_back(folder); +} + + +void IMAPStore::unregisterFolder(IMAPFolder* folder) +{ + std::list ::iterator it = std::find(m_folders.begin(), m_folders.end(), folder); + if (it != m_folders.end()) m_folders.erase(it); +} + + +const int IMAPStore::getCapabilities() const +{ + return (CAPABILITY_CREATE_FOLDER | + CAPABILITY_RENAME_FOLDER | + CAPABILITY_ADD_MESSAGE | + CAPABILITY_COPY_MESSAGE | + CAPABILITY_DELETE_MESSAGE | + CAPABILITY_PARTIAL_FETCH | + CAPABILITY_MESSAGE_FLAGS | + CAPABILITY_EXTRACT_PART); +} + + + + +// Service infos + +IMAPStore::_infos IMAPStore::sm_infos; + + +const serviceInfos& IMAPStore::getInfosInstance() +{ + return (sm_infos); +} + + +const serviceInfos& IMAPStore::getInfos() const +{ + return (sm_infos); +} + + +const port_t IMAPStore::_infos::getDefaultPort() const +{ + return (143); +} + + +const string IMAPStore::_infos::getPropertyPrefix() const +{ + return "store.imap."; +} + + +const std::vector IMAPStore::_infos::getAvailableProperties() const +{ + std::vector list; + + // IMAP-specific options + //list.push_back("auth.mechanism"); + + // Common properties + list.push_back("auth.username"); + list.push_back("auth.password"); + + list.push_back("server.address"); + list.push_back("server.port"); + list.push_back("server.socket-factory"); + + list.push_back("timeout.factory"); + + return (list); +} + + +} // imap +} // messaging +} // vmime diff --git a/src/messaging/imap/IMAPTag.cpp b/src/messaging/imap/IMAPTag.cpp new file mode 100644 index 00000000..bc6b656d --- /dev/null +++ b/src/messaging/imap/IMAPTag.cpp @@ -0,0 +1,99 @@ +// +// 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/imap/IMAPTag.hpp" + + +namespace vmime { +namespace messaging { +namespace imap { + + +const int IMAPTag::sm_maxNumber = 52 * 10 * 10 * 10; + + +IMAPTag::IMAPTag(const int number) + : m_number(number) +{ + m_tag.resize(4); +} + + +IMAPTag::IMAPTag(const IMAPTag& tag) + : m_number(tag.m_number) +{ + m_tag.resize(4); +} + + +IMAPTag::IMAPTag() + : m_number(0) +{ + m_tag.resize(4); +} + + +IMAPTag& IMAPTag::operator++() +{ + ++m_number; + + if (m_number >= sm_maxNumber) + m_number = 1; + + generate(); + + return (*this); +} + + +const IMAPTag IMAPTag::operator++(int) +{ + IMAPTag old(*this); + operator++(); + return (old); +} + + +const int IMAPTag::number() const +{ + return (m_number); +} + + +IMAPTag::operator string() const +{ + return (m_tag); +} + + +void IMAPTag::generate() +{ + static const char prefixChars[53] = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + m_tag[0] = prefixChars[m_number / 1000]; + m_tag[1] = '0' + (m_number % 1000) / 100; + m_tag[2] = '0' + (m_number % 100) / 10; + m_tag[3] = '0' + (m_number % 10); +} + + +} // imap +} // messaging +} // vmime diff --git a/src/messaging/imap/IMAPUtils.cpp b/src/messaging/imap/IMAPUtils.cpp new file mode 100644 index 00000000..3a41598d --- /dev/null +++ b/src/messaging/imap/IMAPUtils.cpp @@ -0,0 +1,555 @@ +// +// 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/imap/IMAPUtils.hpp" +#include "vmime/messaging/message.hpp" + +#include +#include +#include + + +namespace vmime { +namespace messaging { +namespace imap { + + +const string IMAPUtils::quoteString(const string& text) +{ + // + // ATOM_CHAR ::= + // + // atom_specials ::= "(" / ")" / "{" / SPACE / CTL / + // list_wildcards / quoted_specials + // + // list_wildcards ::= "%" / "*" + // + // quoted_specials ::= <"> / "\" + // + // CHAR ::= + // + // CTL ::= + // + + bool needQuoting = text.empty(); + + for (string::const_iterator it = text.begin() ; + !needQuoting && it != text.end() ; ++it) + { + const unsigned char c = *it; + + switch (c) + { + case '(': + case ')': + case '{': + case 0x20: // SPACE + case '%': + case '*': + case '"': + case '\\': + + needQuoting = true; + break; + + default: + + if (c <= 0x1f || c >= 0x7f) + needQuoting = true; + } + } + + if (needQuoting) + { + string quoted; + quoted.reserve((text.length() * 3) / 2 + 2); + + quoted += '"'; + + for (string::const_iterator it = text.begin() ; + !needQuoting && it != text.end() ; ++it) + { + const unsigned char c = *it; + + if (c == '\\' || c == '"') + quoted += '\\'; + + quoted += c; + } + + quoted += '"'; + + return (quoted); + } + else + { + return (text); + } +} + + +const string IMAPUtils::pathToString + (const char hierarchySeparator, const folder::path& path) +{ + string result; + + for (int i = 0 ; i < path.getSize() ; ++i) + { + if (i > 0) result += hierarchySeparator; + result += toModifiedUTF7(hierarchySeparator, path[i]); + } + + return (result); +} + + +const folder::path IMAPUtils::stringToPath + (const char hierarchySeparator, const string& str) +{ + folder::path result; + string::const_iterator begin = str.begin(); + + for (string::const_iterator it = str.begin() ; it != str.end() ; ++it) + { + if (*it == hierarchySeparator) + { + result /= fromModifiedUTF7(string(begin, it)); + begin = it + 1; + } + } + + if (begin != str.end()) + { + result /= fromModifiedUTF7(string(begin, str.end())); + } + + return (result); +} + + +const string IMAPUtils::toModifiedUTF7 + (const char hierarchySeparator, const folder::path::component& text) +{ + // We will replace the hierarchy separator with an equivalent + // UTF-7 sequence, so we compute it here... + const char base64alphabet[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,="; + + const unsigned int hs = static_cast (static_cast (hierarchySeparator)); + + string hsUTF7; + hsUTF7.resize(3); + + hsUTF7[0] = base64alphabet[0]; + hsUTF7[1] = base64alphabet[(hs & 0xF0) >> 4]; + hsUTF7[2] = base64alphabet[(hs & 0x0F) << 2]; + + // Transcode path component to UTF-7 charset. + // WARNING: This may throw "exceptions::charset_conv_error" + const string cvt = text.getConvertedText(charset(charsets::UTF_7)); + + // Transcode to modified UTF-7 (RFC-2060). + string out; + out.reserve((cvt.length() * 3) / 2); + + bool inB64sequence = false; + + for (string::const_iterator it = cvt.begin() ; it != cvt.end() ; ++it) + { + const unsigned char c = *it; + + // Replace hierarchy separator with an equivalent UTF-7 Base64 sequence + if (!inB64sequence && c == hierarchySeparator) + { + out += "&" + hsUTF7 + "-"; + continue; + } + + switch (c) + { + // Beginning of Base64 sequence: replace '+' with '&' + case '+': + { + if (!inB64sequence) + { + inB64sequence = true; + out += '&'; + } + else + { + out += '+'; + } + + break; + } + // End of Base64 sequence + case '-': + { + inB64sequence = false; + out += '-'; + break; + } + // ',' is used instead of '/' in modified Base64 + case '/': + { + out += inB64sequence ? ',' : '/'; + break; + } + // '&' (0x26) is represented by the two-octet sequence "&-" + case '&': + { + if (!inB64sequence) + out += "&-"; + else + out += '&'; + + break; + } + default: + { + out += c; + break; + } + + } + } + + return (out); +} + + +const folder::path::component IMAPUtils::fromModifiedUTF7(const string& text) +{ + // Transcode from modified UTF-7 (RFC-2060). + string out; + out.reserve(text.length()); + + bool inB64sequence = false; + unsigned char prev = 0; + + for (string::const_iterator it = text.begin() ; it != text.end() ; ++it) + { + const unsigned char c = *it; + + switch (c) + { + // Start of Base64 sequence + case '&': + { + if (!inB64sequence) + { + inB64sequence = true; + out += '+'; + } + else + { + out += '&'; + } + + break; + } + // End of Base64 sequence (or "&-" --> "&") + case '-': + { + if (inB64sequence && prev == '&') + out += '&'; + else + out += '-'; + + inB64sequence = false; + break; + } + // ',' is used instead of '/' in modified Base64 + case ',': + { + out += (inB64sequence ? '/' : ','); + break; + } + default: + { + out += c; + break; + } + + } + + prev = c; + } + + // Store it as UTF-8 by default + string cvt; + charset::convert(out, cvt, + charset(charsets::UTF_7), charset(charsets::UTF_8)); + + return (folder::path::component(cvt, charset(charsets::UTF_8))); +} + + +const int IMAPUtils::folderTypeFromFlags(const IMAPParser::mailbox_flag_list* list) +{ + // Get folder type + int type = folder::TYPE_CONTAINS_MESSAGES | folder::TYPE_CONTAINS_FOLDERS; + const std::vector & flags = list->flags(); + + for (std::vector ::const_iterator it = flags.begin() ; + it != flags.end() ; ++it) + { + if ((*it)->type() == IMAPParser::mailbox_flag::NOSELECT) + type &= ~folder::TYPE_CONTAINS_MESSAGES; + } + + if (type & folder::TYPE_CONTAINS_MESSAGES) + type &= ~folder::TYPE_CONTAINS_FOLDERS; + + return (type); +} + + +const int IMAPUtils::folderFlagsFromFlags(const IMAPParser::mailbox_flag_list* list) +{ + // Get folder flags + int folderFlags = folder::FLAG_CHILDREN; + const std::vector & flags = list->flags(); + + for (std::vector ::const_iterator it = flags.begin() ; + it != flags.end() ; ++it) + { + if ((*it)->type() == IMAPParser::mailbox_flag::NOSELECT) + folderFlags |= folder::FLAG_NO_OPEN; + else if ((*it)->type() == IMAPParser::mailbox_flag::NOINFERIORS) + folderFlags &= ~folder::FLAG_CHILDREN; + } + + return (folderFlags); +} + + +const int IMAPUtils::messageFlagsFromFlags(const IMAPParser::flag_list* list) +{ + const std::vector & flagList = list->flags(); + int flags = 0; + + for (std::vector ::const_iterator + it = flagList.begin() ; it != flagList.end() ; ++it) + { + switch ((*it)->type()) + { + case IMAPParser::flag::ANSWERED: + flags |= message::FLAG_REPLIED; + break; + case IMAPParser::flag::FLAGGED: + flags |= message::FLAG_MARKED; + break; + case IMAPParser::flag::DELETED: + flags |= message::FLAG_DELETED; + break; + case IMAPParser::flag::SEEN: + flags |= message::FLAG_SEEN; + break; + + default: + //case IMAPParser::flag::UNKNOWN: + //case IMAPParser::flag::DRAFT: + break; + } + } + + return (flags); +} + + +const string IMAPUtils::messageFlagList(const int flags) +{ + std::vector flagList; + + if (flags & message::FLAG_REPLIED) flagList.push_back("\\Answered"); + if (flags & message::FLAG_MARKED) flagList.push_back("\\Flagged"); + if (flags & message::FLAG_DELETED) flagList.push_back("\\Deleted"); + if (flags & message::FLAG_SEEN) flagList.push_back("\\Seen"); + + if (!flagList.empty()) + { + std::ostringstream res; + res << "("; + + if (flagList.size() >= 2) + { + std::copy(flagList.begin(), flagList.end() - 1, + std::ostream_iterator (res, " ")); + } + + res << *(flagList.end() - 1) << ")"; + + return (res.str()); + } + + return ""; +} + + +// This function builds a "IMAP set" given a list. Try to group consecutive +// message numbers to reduce the list. +// +// Example: +// IN = "1,2,3,4,5,7,8,13,15,16,17" +// OUT = "1:5,7:8,13,15:*" for a mailbox with a total of 17 messages (max = 17) + +const string IMAPUtils::listToSet(const std::vector & list, const int max, + const bool alreadySorted) +{ + // Sort a copy of the list (if not already sorted) + std::vector temp; + + if (!alreadySorted) + { + temp.resize(list.size()); + std::copy(list.begin(), list.end(), temp.begin()); + + std::sort(temp.begin(), temp.end()); + } + + const std::vector & theList = (alreadySorted ? list : temp); + + // Build the set + std::ostringstream res; + int previous = -1, setBegin = -1; + + for (std::vector ::const_iterator it = theList.begin() ; + it != theList.end() ; ++it) + { + const int current = *it; + + if (previous == -1) + { + res << current; + + previous = current; + setBegin = current; + } + else + { + if (current == previous + 1) + { + previous = current; + } + else + { + if (setBegin != previous) + { + res << ":" << previous << "," << current; + + previous = current; + setBegin = current; + } + else + { + if (setBegin != current) // skip duplicates + res << "," << current; + + previous = current; + setBegin = current; + } + } + } + } + + if (previous != setBegin) + { + if (previous == max) + res << ":*"; + else + res << ":" << previous; + } + + return (res.str()); +} + + +const string IMAPUtils::dateTime(const vmime::datetime& date) +{ + std::ostringstream res; + + // date_time ::= <"> date_day_fixed "-" date_month "-" date_year + // SPACE time SPACE zone <"> + // + // time ::= 2digit ":" 2digit ":" 2digit + // ;; Hours minutes seconds + // zone ::= ("+" / "-") 4digit + // ;; Signed four-digit value of hhmm representing + // ;; hours and minutes west of Greenwich + res << '"'; + + // Date + if (date.getDay() < 10) res << ' '; + res << date.getDay(); + + res << '-'; + + static const char* monthNames[12] = + { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + + res << monthNames[std::min(std::max(date.getMonth() - 1, 0), 11)]; + + res << '-'; + + if (date.getYear() < 10) res << '0'; + if (date.getYear() < 100) res << '0'; + if (date.getYear() < 1000) res << '0'; + res << date.getYear(); + + res << ' '; + + // Time + if (date.getHour() < 10) res << '0'; + res << date.getHour() << ':'; + + if (date.getMinute() < 10) res << '0'; + res << date.getMinute() << ':'; + + if (date.getSecond() < 10) res << '0'; + res << date.getSecond(); + + res << ' '; + + // Zone + const int zs = (date.getZone() < 0 ? -1 : 1); + const int zh = (date.getZone() * zs) / 60; + const int zm = (date.getZone() * zs) % 60; + + res << (zs < 0 ? '-' : '+'); + + if (zh < 10) res << '0'; + res << zh; + + if (zm < 10) res << '0'; + res << zm; + + res << '"'; + + + return (res.str()); +} + + +} // imap +} // messaging +} // vmime diff --git a/src/messaging/maildir/maildirFolder.cpp b/src/messaging/maildir/maildirFolder.cpp new file mode 100644 index 00000000..0edb0f4c --- /dev/null +++ b/src/messaging/maildir/maildirFolder.cpp @@ -0,0 +1,1350 @@ +// +// 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/maildir/maildirFolder.hpp" + +#include "vmime/messaging/maildir/maildirStore.hpp" +#include "vmime/messaging/maildir/maildirMessage.hpp" +#include "vmime/messaging/maildir/maildirUtils.hpp" + +#include "vmime/utility/smartPtr.hpp" + +#include "vmime/message.hpp" + +#include "vmime/exception.hpp" +#include "vmime/platformDependant.hpp" + + +namespace vmime { +namespace messaging { +namespace maildir { + + +maildirFolder::maildirFolder(const folder::path& path, maildirStore* store) + : m_store(store), m_path(path), + m_name(path.isEmpty() ? folder::path::component("") : path.getLastComponent()), + m_mode(-1), m_open(false), m_unreadMessageCount(0), m_messageCount(0) +{ + m_store->registerFolder(this); +} + + +maildirFolder::~maildirFolder() +{ + if (m_store) + { + if (m_open) + close(false); + + m_store->unregisterFolder(this); + } + else if (m_open) + { + close(false); + } +} + + +void maildirFolder::onStoreDisconnected() +{ + m_store = NULL; +} + + +const int maildirFolder::getMode() const +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + return (m_mode); +} + + +const int maildirFolder::getType() +{ + if (m_path.isEmpty()) + return (TYPE_CONTAINS_FOLDERS); + else + return (TYPE_CONTAINS_FOLDERS | TYPE_CONTAINS_MESSAGES); +} + + +const int maildirFolder::getFlags() +{ + int flags = 0; + + utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); + + utility::auto_ptr rootDir = fsf->create + (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_CONTAINER)); + + utility::auto_ptr it = rootDir->getFiles(); + + while (it->hasMoreElements()) + { + utility::auto_ptr file = it->nextElement(); + + if (maildirUtils::isSubfolderDirectory(*file)) + { + flags |= FLAG_CHILDREN; // Contains at least one sub-folder + break; + } + } + + return (flags); +} + + +const folder::path::component maildirFolder::getName() const +{ + return (m_name); +} + + +const folder::path maildirFolder::getFullPath() const +{ + return (m_path); +} + + +void maildirFolder::open(const int mode, bool /* failIfModeIsNotAvailable */) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (isOpen()) + throw exceptions::illegal_state("Folder is already open"); + else if (!exists()) + throw exceptions::illegal_state("Folder does not exist"); + + scanFolder(); + + m_open = true; + m_mode = mode; +} + + +void maildirFolder::close(const bool expunge) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + if (expunge) + this->expunge(); + + m_open = false; + m_mode = -1; + + onClose(); +} + + +void maildirFolder::onClose() +{ + for (std::vector ::iterator it = m_messages.begin() ; + it != m_messages.end() ; ++it) + { + (*it)->onFolderClosed(); + } + + m_messages.clear(); +} + + +void maildirFolder::registerMessage(maildirMessage* msg) +{ + m_messages.push_back(msg); +} + + +void maildirFolder::unregisterMessage(maildirMessage* msg) +{ + std::vector ::iterator it = + std::find(m_messages.begin(), m_messages.end(), msg); + + if (it != m_messages.end()) + m_messages.erase(it); +} + + +void maildirFolder::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(); + + // Create directory on file system + try + { + utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); + + if (!fsf->isValidPath(maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_ROOT))) + throw exceptions::invalid_folder_name(); + + utility::auto_ptr rootDir = fsf->create + (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_ROOT)); + + utility::auto_ptr newDir = fsf->create + (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_NEW)); + utility::auto_ptr tmpDir = fsf->create + (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_TMP)); + utility::auto_ptr curDir = fsf->create + (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_CUR)); + + rootDir->createDirectory(true); + + newDir->createDirectory(false); + tmpDir->createDirectory(false); + curDir->createDirectory(false); + } + catch (exceptions::filesystem_exception& e) + { + throw exceptions::command_error("CREATE", "", "File system exception", e); + } + + // Notify folder created + events::folderEvent event(this, events::folderEvent::TYPE_CREATED, m_path, m_path); + notifyFolder(event); +} + + +const bool maildirFolder::exists() +{ + utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); + + utility::auto_ptr rootDir = fsf->create + (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_ROOT)); + + utility::auto_ptr newDir = fsf->create + (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_NEW)); + utility::auto_ptr tmpDir = fsf->create + (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_TMP)); + utility::auto_ptr curDir = fsf->create + (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_CUR)); + + return (rootDir->exists() && rootDir->isDirectory() && + newDir->exists() && newDir->isDirectory() && + tmpDir->exists() && tmpDir->isDirectory() && + curDir->exists() && curDir->isDirectory()); +} + + +const bool maildirFolder::isOpen() const +{ + return (m_open); +} + + +void maildirFolder::scanFolder() +{ + try + { + m_messageCount = 0; + m_unreadMessageCount = 0; + + utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); + + utility::file::path newDirPath = maildirUtils::getFolderFSPath + (m_store, m_path, maildirUtils::FOLDER_PATH_NEW); + utility::auto_ptr newDir = fsf->create(newDirPath); + + utility::file::path curDirPath = maildirUtils::getFolderFSPath + (m_store, m_path, maildirUtils::FOLDER_PATH_CUR); + utility::auto_ptr curDir = fsf->create(curDirPath); + + // New received messages (new/) + utility::fileIterator* nit = newDir->getFiles(); + std::vector newMessageFilenames; + + while (nit->hasMoreElements()) + { + utility::auto_ptr file = nit->nextElement(); + newMessageFilenames.push_back(file->getFullPath().getLastComponent()); + } + + delete (nit); // Free directory + + // Current messages (cur/) + utility::fileIterator* cit = curDir->getFiles(); + std::vector curMessageFilenames; + + while (cit->hasMoreElements()) + { + utility::auto_ptr file = cit->nextElement(); + curMessageFilenames.push_back(file->getFullPath().getLastComponent()); + } + + delete (cit); // Free directory + + // Update/delete existing messages (found in previous scan) + for (unsigned int i = 0 ; i < m_messageInfos.size() ; ++i) + { + messageInfos& msgInfos = m_messageInfos[i]; + + // NOTE: the flags may have changed (eg. moving from 'new' to 'cur' + // may imply the 'S' flag) and so the filename. That's why we use + // "maildirUtils::messageIdComparator" to compare only the 'unique' + // portion of the filename... + + if (msgInfos.type == messageInfos::TYPE_CUR) + { + const std::vector ::iterator pos = + std::find_if(curMessageFilenames.begin(), curMessageFilenames.end(), + maildirUtils::messageIdComparator(msgInfos.path)); + + // If we cannot find this message in the 'cur' directory, + // it means it has been deleted (and expunged). + if (pos == curMessageFilenames.end()) + { + msgInfos.type = messageInfos::TYPE_DELETED; + } + // Otherwise, update its information. + else + { + msgInfos.path = *pos; + curMessageFilenames.erase(pos); + } + } + } + + m_messageInfos.reserve(m_messageInfos.size() + + newMessageFilenames.size() + curMessageFilenames.size()); + + // Add new messages from 'new': we are responsible to move the files + // from the 'new' directory to the 'cur' directory, and append them + // to our message list. + for (std::vector ::const_iterator + it = newMessageFilenames.begin() ; it != newMessageFilenames.end() ; ++it) + { + const utility::file::path::component newFilename = + maildirUtils::buildFilename(maildirUtils::extractId(*it), 0); + + // Move messages from 'new' to 'cur' + utility::auto_ptr file = fsf->create(newDirPath / *it); + file->rename(curDirPath / newFilename); + + // Append to message list + messageInfos msgInfos; + msgInfos.path = newFilename; + msgInfos.type = messageInfos::TYPE_CUR; + + m_messageInfos.push_back(msgInfos); + } + + // Add new messages from 'cur': the files have already been moved + // from 'new' to 'cur'. Just append them to our message list. + for (std::vector ::const_iterator + it = curMessageFilenames.begin() ; it != curMessageFilenames.end() ; ++it) + { + // Append to message list + messageInfos msgInfos; + msgInfos.path = *it; + msgInfos.type = messageInfos::TYPE_CUR; + + m_messageInfos.push_back(msgInfos); + } + + // Update message count + int unreadMessageCount = 0; + + for (std::vector ::const_iterator + it = m_messageInfos.begin() ; it != m_messageInfos.end() ; ++it) + { + if ((maildirUtils::extractFlags((*it).path) & message::FLAG_SEEN) == 0) + ++unreadMessageCount; + } + + m_unreadMessageCount = unreadMessageCount; + m_messageCount = m_messageInfos.size(); + } + catch (exceptions::filesystem_exception&) + { + // Should not happen... + } +} + + +message* maildirFolder::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 maildirMessage(this, num); +} + + +std::vector maildirFolder::getMessages(const int from, const int to) +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + std::vector v; + + for (int i = from ; i <= to ; ++i) + v.push_back(new maildirMessage(this, i)); + + return (v); +} + + +std::vector maildirFolder::getMessages(const std::vector & nums) +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + std::vector v; + + for (std::vector ::const_iterator it = nums.begin() ; it != nums.end() ; ++it) + v.push_back(new maildirMessage(this, *it)); + + return (v); +} + + +const int maildirFolder::getMessageCount() +{ + return (m_messageCount); +} + + +folder* maildirFolder::getFolder(const folder::path::component& name) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + + return new maildirFolder(m_path / name, m_store); +} + + +std::vector maildirFolder::getFolders(const bool recursive) +{ + if (!isOpen() && !m_store) + throw exceptions::illegal_state("Store disconnected"); + + std::vector list; + + try + { + listFolders(list, recursive); + } + catch (std::exception&) + { + for (std::vector ::iterator it = list.begin() ; it != list.end() ; ++it) + delete (*it); + + throw; + } + catch (vmime::exception&) + { + for (std::vector ::iterator it = list.begin() ; it != list.end() ; ++it) + delete (*it); + + throw; + } + + return (list); +} + + +void maildirFolder::listFolders(std::vector & list, const bool recursive) +{ + try + { + utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); + + utility::auto_ptr rootDir = fsf->create + (maildirUtils::getFolderFSPath(m_store, m_path, + m_path.isEmpty() ? maildirUtils::FOLDER_PATH_ROOT + : maildirUtils::FOLDER_PATH_CONTAINER)); + + if (rootDir->exists()) + { + utility::auto_ptr it = rootDir->getFiles(); + + while (it->hasMoreElements()) + { + utility::auto_ptr file = it->nextElement(); + + if (maildirUtils::isSubfolderDirectory(*file)) + { + const utility::path subPath = m_path / file->getFullPath().getLastComponent(); + maildirFolder* subFolder = new maildirFolder(subPath, m_store); + + list.push_back(subFolder); + + if (recursive) + subFolder->listFolders(list, true); + } + } + } + else + { + // No sub-folder + } + } + catch (exceptions::filesystem_exception& e) + { + throw exceptions::command_error("LIST", "", "", e); + } +} + + +void maildirFolder::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_store->isValidFolderName(newPath.getLastComponent())) + throw exceptions::invalid_folder_name(); + + // Rename the directory on the file system + utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); + + utility::auto_ptr rootDir = fsf->create + (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_ROOT)); + utility::auto_ptr contDir = fsf->create + (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_CONTAINER)); + + try + { + const utility::file::path newRootPath = + maildirUtils::getFolderFSPath(m_store, newPath, maildirUtils::FOLDER_PATH_ROOT); + const utility::file::path newContPath = + maildirUtils::getFolderFSPath(m_store, newPath, maildirUtils::FOLDER_PATH_CONTAINER); + + rootDir->rename(newRootPath); + + // Container directory may not exist, so ignore error when trying to rename it + try + { + contDir->rename(newContPath); + } + catch (exceptions::filesystem_exception& e) + { + // Ignore + } + } + catch (exceptions::filesystem_exception& e) + { + // Revert to old location + const utility::file::path rootPath = + maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_ROOT); + const utility::file::path contPath = + maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_CONTAINER); + + try + { + rootDir->rename(rootPath); + contDir->rename(contPath); + } + catch (exceptions::filesystem_exception& e) + { + // Ignore + } + + throw exceptions::command_error("RENAME", "", "", e); + } + + // 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 + for (std::list ::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 maildirFolder::deleteMessage(const int num) +{ + // Mark messages as deleted + setMessageFlags(num, num, message::FLAG_MODE_ADD, message::FLAG_DELETED); +} + + +void maildirFolder::deleteMessages(const int from, const int to) +{ + // Mark messages as deleted + setMessageFlags(from, to, message::FLAG_MODE_ADD, message::FLAG_DELETED); +} + + +void maildirFolder::deleteMessages(const std::vector & nums) +{ + // Mark messages as deleted + setMessageFlags(nums, message::FLAG_MODE_ADD, message::FLAG_DELETED); +} + + +void maildirFolder::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"); + + // Construct the list of message numbers + const int to2 = (to == -1) ? m_messageCount : to; + const int count = to - from + 1; + + std::vector nums; + nums.resize(count); + + for (int i = from, j = 0 ; i <= to2 ; ++i, ++j) + nums[j] = i; + + // Change message flags + setMessageFlagsImpl(nums, flags, mode); + + // Update local flags + switch (mode) + { + case message::FLAG_MODE_ADD: + { + for (std::vector ::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 ::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 ::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 + events::messageChangedEvent event(this, events::messageChangedEvent::TYPE_FLAGS, nums); + + notifyMessageChanged(event); +} + + +void maildirFolder::setMessageFlags + (const std::vector & 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 list; + + list.resize(nums.size()); + std::copy(nums.begin(), nums.end(), list.begin()); + + std::sort(list.begin(), list.end()); + + // Change message flags + setMessageFlagsImpl(list, flags, mode); + + // Update local flags + switch (mode) + { + case message::FLAG_MODE_ADD: + { + for (std::vector ::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 ::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 ::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 maildirFolder::setMessageFlagsImpl + (const std::vector & nums, const int flags, const int mode) +{ + utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); + + utility::file::path curDirPath = maildirUtils::getFolderFSPath + (m_store, m_path, maildirUtils::FOLDER_PATH_CUR); + + for (std::vector ::const_iterator it = + nums.begin() ; it != nums.end() ; ++it) + { + const int num = *it - 1; + + try + { + const utility::file::path::component path = m_messageInfos[num].path; + utility::auto_ptr file = fsf->create(curDirPath / path); + + int newFlags = maildirUtils::extractFlags(path); + + switch (mode) + { + case message::FLAG_MODE_ADD: newFlags |= flags; break; + case message::FLAG_MODE_REMOVE: newFlags &= ~flags; break; + default: + case message::FLAG_MODE_SET: newFlags = flags; break; + } + + const utility::file::path::component newPath = maildirUtils::buildFilename + (maildirUtils::extractId(path), newFlags); + + file->rename(curDirPath / newPath); + + if (flags & message::FLAG_DELETED) + m_messageInfos[num].type = messageInfos::TYPE_DELETED; + else + m_messageInfos[num].type = messageInfos::TYPE_CUR; + + m_messageInfos[num].path = newPath; + } + catch (exceptions::filesystem_exception& e) + { + // Ignore (not important) + } + } +} + + +void maildirFolder::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 maildirFolder::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"); + + utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); + + utility::file::path tmpDirPath = maildirUtils::getFolderFSPath + (m_store, m_path, maildirUtils::FOLDER_PATH_TMP); + utility::file::path curDirPath = maildirUtils::getFolderFSPath + (m_store, m_path, maildirUtils::FOLDER_PATH_CUR); + + const utility::file::path::component filename = + maildirUtils::buildFilename(maildirUtils::generateId(), + ((flags == message::FLAG_UNDEFINED) ? 0 : flags)); + + try + { + utility::auto_ptr tmpDir = fsf->create(tmpDirPath); + tmpDir->createDirectory(true); + } + catch (exceptions::filesystem_exception&) + { + // Don't throw now, it will fail later... + } + + try + { + utility::auto_ptr curDir = fsf->create(curDirPath); + curDir->createDirectory(true); + } + catch (exceptions::filesystem_exception&) + { + // Don't throw now, it will fail later... + } + + // Actually add the message + copyMessageImpl(tmpDirPath, curDirPath, filename, is, size, progress); + + // Append the message to the cache list + messageInfos msgInfos; + msgInfos.path = filename; + msgInfos.type = messageInfos::TYPE_CUR; + + m_messageInfos.push_back(msgInfos); + m_messageCount++; + + if ((flags == message::FLAG_UNDEFINED) || !(flags & message::FLAG_SEEN)) + m_unreadMessageCount++; + + // Notification + std::vector nums; + nums.push_back(m_messageCount); + + events::messageCountEvent event(this, events::messageCountEvent::TYPE_ADDED, nums); + + notifyMessageCount(event); + + // Notify folders with the same path + for (std::list ::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; + (*it)->m_unreadMessageCount = m_unreadMessageCount; + + (*it)->m_messageInfos.resize(m_messageInfos.size()); + std::copy(m_messageInfos.begin(), m_messageInfos.end(), (*it)->m_messageInfos.begin()); + + events::messageCountEvent event(*it, events::messageCountEvent::TYPE_ADDED, nums); + + (*it)->notifyMessageCount(event); + } + } +} + + +void maildirFolder::copyMessageImpl(const utility::file::path& tmpDirPath, + const utility::file::path& curDirPath, const utility::file::path::component& filename, + utility::inputStream& is, const utility::stream::size_type size, progressionListener* progress) +{ + utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); + + utility::auto_ptr file = fsf->create(tmpDirPath / filename); + + if (progress) + progress->start(size); + + // First, write the message into 'tmp'... + try + { + file->createFile(); + + utility::auto_ptr fw = file->getFileWriter(); + utility::auto_ptr os = fw->getOutputStream(); + + utility::stream::value_type buffer[65536]; + utility::stream::size_type total = 0; + + while (!is.eof()) + { + const utility::stream::size_type read = is.read(buffer, sizeof(buffer)); + + if (read != 0) + { + os->write(buffer, read); + total += read; + } + + if (progress) + progress->progress(total, size); + } + } + catch (exception& e) + { + if (progress) + progress->stop(size); + + // Delete temporary file + try + { + utility::auto_ptr file = fsf->create(tmpDirPath / filename); + file->remove(); + } + catch (exceptions::filesystem_exception&) + { + // Ignore + } + + throw exceptions::command_error("ADD", "", "", e); + } + + // ...then, move it to 'cur' + try + { + file->rename(curDirPath / filename); + } + catch (exception& e) + { + if (progress) + progress->stop(size); + + // Delete temporary file + try + { + utility::auto_ptr file = fsf->create(tmpDirPath / filename); + file->remove(); + } + catch (exceptions::filesystem_exception&) + { + // Ignore + } + + throw exceptions::command_error("ADD", "", "", e); + } + + if (progress) + progress->stop(size); +} + + +void maildirFolder::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"); + + copyMessages(dest, num, num); +} + + +void maildirFolder::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 the list of message numbers + const int to2 = (to == -1) ? m_messageCount : to; + const int count = to - from + 1; + + std::vector nums; + nums.resize(count); + + for (int i = from, j = 0 ; i <= to2 ; ++i, ++j) + nums[j] = i; + + // Copy messages + copyMessagesImpl(dest, nums); +} + + +void maildirFolder::copyMessages(const folder::path& dest, const std::vector & nums) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + // Copy messages + copyMessagesImpl(dest, nums); +} + + +void maildirFolder::copyMessagesImpl(const folder::path& dest, const std::vector & nums) +{ + utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); + + utility::file::path curDirPath = maildirUtils::getFolderFSPath + (m_store, m_path, maildirUtils::FOLDER_PATH_CUR); + + utility::file::path destCurDirPath = maildirUtils::getFolderFSPath + (m_store, dest, maildirUtils::FOLDER_PATH_CUR); + utility::file::path destTmpDirPath = maildirUtils::getFolderFSPath + (m_store, dest, maildirUtils::FOLDER_PATH_TMP); + + // Create destination directories + try + { + utility::auto_ptr destTmpDir = fsf->create(destTmpDirPath); + destTmpDir->createDirectory(true); + } + catch (exceptions::filesystem_exception&) + { + // Don't throw now, it will fail later... + } + + try + { + utility::auto_ptr destCurDir = fsf->create(destCurDirPath); + destCurDir->createDirectory(true); + } + catch (exceptions::filesystem_exception&) + { + // Don't throw now, it will fail later... + } + + // Copy messages + try + { + for (std::vector ::const_iterator it = + nums.begin() ; it != nums.end() ; ++it) + { + const int num = *it; + const messageInfos& msg = m_messageInfos[num - 1]; + const int flags = maildirUtils::extractFlags(msg.path); + + const utility::file::path::component filename = + maildirUtils::buildFilename(maildirUtils::generateId(), flags); + + utility::auto_ptr file = fsf->create(curDirPath / msg.path); + utility::auto_ptr fr = file->getFileReader(); + utility::auto_ptr is = fr->getInputStream(); + + copyMessageImpl(destTmpDirPath, destCurDirPath, + filename, *is, file->getLength(), NULL); + } + } + catch (exception& e) + { + notifyMessagesCopied(dest); + throw exceptions::command_error("COPY", "", "", e); + } + + notifyMessagesCopied(dest); +} + + +void maildirFolder::notifyMessagesCopied(const folder::path& dest) +{ + for (std::list ::iterator it = m_store->m_folders.begin() ; + it != m_store->m_folders.end() ; ++it) + { + if ((*it) != this && (*it)->getFullPath() == dest) + { + // We only need to update the first folder we found as calling + // status() will notify all the folders with the same path. + int count, unseen; + (*it)->status(count, unseen); + + return; + } + } +} + + +void maildirFolder::status(int& count, int& unseen) +{ + const int oldCount = m_messageCount; + + scanFolder(); + + count = m_messageCount; + unseen = m_unreadMessageCount; + + // Notify message count changed (new messages) + if (count > oldCount) + { + std::vector 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 ::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; + (*it)->m_unreadMessageCount = m_unreadMessageCount; + + (*it)->m_messageInfos.resize(m_messageInfos.size()); + std::copy(m_messageInfos.begin(), m_messageInfos.end(), (*it)->m_messageInfos.begin()); + + events::messageCountEvent event(*it, events::messageCountEvent::TYPE_ADDED, nums); + + (*it)->notifyMessageCount(event); + } + } + } +} + + +void maildirFolder::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"); + + utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); + + utility::file::path curDirPath = maildirUtils::getFolderFSPath + (m_store, m_path, maildirUtils::FOLDER_PATH_CUR); + + std::vector nums; + int unreadCount = 0; + + for (int num = 1 ; num <= m_messageCount ; ++num) + { + messageInfos& infos = m_messageInfos[num - 1]; + + if (infos.type == messageInfos::TYPE_DELETED) + { + nums.push_back(num); + + for (std::vector ::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + if ((*it)->m_num == num) + (*it)->m_expunged = true; + else if ((*it)->m_num > num) + (*it)->m_num--; + } + + if (maildirUtils::extractFlags(infos.path) & message::FLAG_SEEN) + ++unreadCount; + + // Delete file from file system + try + { + utility::auto_ptr file = fsf->create(curDirPath / infos.path); + file->remove(); + } + catch (exceptions::filesystem_exception& e) + { + // Ignore (not important) + } + } + } + + if (!nums.empty()) + { + for (int i = nums.size() - 1 ; i >= 0 ; --i) + m_messageInfos.erase(m_messageInfos.begin() + i); + } + + m_messageCount -= nums.size(); + m_unreadMessageCount -= unreadCount; + + // Notify message expunged + events::messageCountEvent event(this, events::messageCountEvent::TYPE_REMOVED, nums); + + notifyMessageCount(event); + + // Notify folders with the same path + for (std::list ::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; + (*it)->m_unreadMessageCount = m_unreadMessageCount; + + (*it)->m_messageInfos.resize(m_messageInfos.size()); + std::copy(m_messageInfos.begin(), m_messageInfos.end(), (*it)->m_messageInfos.begin()); + + events::messageCountEvent event(*it, events::messageCountEvent::TYPE_REMOVED, nums); + + (*it)->notifyMessageCount(event); + } + } +} + + +folder* maildirFolder::getParent() +{ + return (m_path.isEmpty() ? NULL : new maildirFolder(m_path.getParent(), m_store)); +} + + +const store* maildirFolder::getStore() const +{ + return (m_store); +} + + +store* maildirFolder::getStore() +{ + return (m_store); +} + + +void maildirFolder::fetchMessages(std::vector & 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 ::iterator it = msg.begin() ; + it != msg.end() ; ++it) + { + dynamic_cast (*it)->fetch(this, options); + + if (progress) + progress->progress(++current, total); + } + + if (progress) + progress->stop(total); +} + + +void maildirFolder::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 (msg)->fetch(this, options); +} + + +const int maildirFolder::getFetchCapabilities() const +{ + return (FETCH_ENVELOPE | FETCH_STRUCTURE | FETCH_CONTENT_INFO | + FETCH_FLAGS | FETCH_SIZE | FETCH_FULL_HEADER | FETCH_UID); +} + + +const utility::file::path maildirFolder::getMessageFSPath(const int number) +{ + utility::file::path curDirPath = maildirUtils::getFolderFSPath + (m_store, m_path, maildirUtils::FOLDER_PATH_CUR); + + return (curDirPath / m_messageInfos[number - 1].path); +} + + +} // maildir +} // messaging +} // vmime diff --git a/src/messaging/maildir/maildirMessage.cpp b/src/messaging/maildir/maildirMessage.cpp new file mode 100644 index 00000000..9b728359 --- /dev/null +++ b/src/messaging/maildir/maildirMessage.cpp @@ -0,0 +1,505 @@ +// +// 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/maildir/maildirMessage.hpp" +#include "vmime/messaging/maildir/maildirFolder.hpp" +#include "vmime/messaging/maildir/maildirUtils.hpp" + +#include "vmime/message.hpp" + +#include "vmime/exception.hpp" +#include "vmime/platformDependant.hpp" + + +namespace vmime { +namespace messaging { +namespace maildir { + + +// +// maildirPart +// + +class maildirStructure; + +class maildirPart : public part +{ +public: + + maildirPart(maildirPart* parent, const int number, const bodyPart& part); + ~maildirPart(); + + + const structure& getStructure() const; + structure& getStructure(); + + const maildirPart* 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); + } + + header& getOrCreateHeader() + { + if (m_header != NULL) + return (*m_header); + else + return (*(m_header = new header())); + } + + const int getHeaderParsedOffset() const { return (m_headerParsedOffset); } + const int getHeaderParsedLength() const { return (m_headerParsedLength); } + + const int getBodyParsedOffset() const { return (m_bodyParsedOffset); } + const int getBodyParsedLength() const { return (m_bodyParsedLength); } + +private: + + maildirStructure* m_structure; + maildirPart* m_parent; + header* m_header; + + int m_number; + int m_size; + mediaType m_mediaType; + + int m_headerParsedOffset; + int m_headerParsedLength; + + int m_bodyParsedOffset; + int m_bodyParsedLength; +}; + + + +// +// maildirStructure +// + +class maildirStructure : public structure +{ +private: + + maildirStructure() + { + } + +public: + + maildirStructure(maildirPart* parent, const bodyPart& part) + { + m_parts.push_back(new maildirPart(parent, 1, part)); + } + + maildirStructure(maildirPart* parent, const std::vector & list) + { + int number = 1; + + for (unsigned int i = 0 ; i < list.size() ; ++i) + m_parts.push_back(new maildirPart(parent, number, *list[i])); + } + + ~maildirStructure() + { + 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 maildirStructure* emptyStructure() + { + return (&m_emptyStructure); + } + +private: + + static maildirStructure m_emptyStructure; + + std::vector m_parts; +}; + + +maildirStructure maildirStructure::m_emptyStructure; + + + +maildirPart::maildirPart(maildirPart* parent, const int number, const bodyPart& part) + : m_parent(parent), m_header(NULL), m_number(number) +{ + if (part.getBody()->getPartList().size() == 0) + m_structure = NULL; + else + m_structure = new maildirStructure(this, part.getBody()->getPartList()); + + m_headerParsedOffset = part.getHeader()->getParsedOffset(); + m_headerParsedLength = part.getHeader()->getParsedLength(); + + m_bodyParsedOffset = part.getBody()->getParsedOffset(); + m_bodyParsedLength = part.getBody()->getParsedLength(); + + m_size = part.getBody()->getContents().getLength(); + + m_mediaType = part.getBody()->getContentType(); +} + + +maildirPart::~maildirPart() +{ + delete (m_structure); + delete (m_header); +} + + +const structure& maildirPart::getStructure() const +{ + if (m_structure != NULL) + return (*m_structure); + else + return (*maildirStructure::emptyStructure()); +} + + +structure& maildirPart::getStructure() +{ + if (m_structure != NULL) + return (*m_structure); + else + return (*maildirStructure::emptyStructure()); +} + + + +// +// maildirMessage +// + +maildirMessage::maildirMessage(maildirFolder* 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); +} + + +maildirMessage::~maildirMessage() +{ + if (m_folder) + m_folder->unregisterMessage(this); + + delete (m_header); + delete (m_structure); +} + + +void maildirMessage::onFolderClosed() +{ + m_folder = NULL; +} + + +const int maildirMessage::getNumber() const +{ + return (m_num); +} + + +const message::uid maildirMessage::getUniqueId() const +{ + return (m_uid); +} + + +const int maildirMessage::getSize() const +{ + if (m_size == -1) + throw exceptions::unfetched_object(); + + return (m_size); +} + + +const bool maildirMessage::isExpunged() const +{ + return (m_expunged); +} + + +const structure& maildirMessage::getStructure() const +{ + if (m_structure == NULL) + throw exceptions::unfetched_object(); + + return (*m_structure); +} + + +structure& maildirMessage::getStructure() +{ + if (m_structure == NULL) + throw exceptions::unfetched_object(); + + return (*m_structure); +} + + +const header& maildirMessage::getHeader() const +{ + if (m_header == NULL) + throw exceptions::unfetched_object(); + + return (*m_header); +} + + +const int maildirMessage::getFlags() const +{ + if (m_flags == FLAG_UNDEFINED) + throw exceptions::unfetched_object(); + + return (m_flags); +} + + +void maildirMessage::setFlags(const int flags, const int mode) +{ + if (!m_folder) + throw exceptions::folder_not_found(); + + m_folder->setMessageFlags(m_num, m_num, flags, mode); +} + + +void maildirMessage::extract(utility::outputStream& os, + progressionListener* progress, const int start, const int length) const +{ + extractImpl(os, progress, 0, m_size, start, length); +} + + +void maildirMessage::extractPart(const part& p, utility::outputStream& os, + progressionListener* progress, const int start, const int length) const +{ + const maildirPart& mp = dynamic_cast (p); + + extractImpl(os, progress, mp.getBodyParsedOffset(), mp.getBodyParsedLength(), start, length); +} + + +void maildirMessage::extractImpl(utility::outputStream& os, progressionListener* progress, + const int start, const int length, const int partialStart, const int partialLength) const +{ + utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); + + const utility::file::path path = m_folder->getMessageFSPath(m_num); + utility::auto_ptr file = fsf->create(path); + + utility::auto_ptr reader = file->getFileReader(); + utility::auto_ptr is = reader->getInputStream(); + + is->skip(start + partialStart); + + utility::stream::value_type buffer[8192]; + utility::stream::size_type remaining = (partialLength == -1 ? length + : std::min(partialLength, length)); + + const int total = remaining; + int current = 0; + + if (progress) + progress->start(total); + + while (!is->eof() && remaining > 0) + { + const utility::stream::size_type read = + is->read(buffer, std::min(remaining, sizeof(buffer))); + + remaining -= read; + current += read; + + os.write(buffer, read); + + if (progress) + progress->progress(current, total); + } + + if (progress) + progress->stop(total); +} + + +void maildirMessage::fetchPartHeader(part& p) +{ + maildirPart& mp = dynamic_cast (p); + + utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); + + const utility::file::path path = m_folder->getMessageFSPath(m_num); + utility::auto_ptr file = fsf->create(path); + + utility::auto_ptr reader = file->getFileReader(); + utility::auto_ptr is = reader->getInputStream(); + + is->skip(mp.getHeaderParsedOffset()); + + utility::stream::value_type buffer[1024]; + utility::stream::size_type remaining = mp.getHeaderParsedLength(); + + string contents; + contents.reserve(remaining); + + while (!is->eof() && remaining > 0) + { + const utility::stream::size_type read = + is->read(buffer, std::min(remaining, sizeof(buffer))); + + remaining -= read; + + contents.append(buffer, read); + } + + mp.getOrCreateHeader().parse(contents); +} + + +void maildirMessage::fetch(maildirFolder* folder, const int options) +{ + if (m_folder != folder) + throw exceptions::folder_not_found(); + + utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); + + const utility::file::path path = folder->getMessageFSPath(m_num); + utility::auto_ptr file = fsf->create(path); + + if (options & folder::FETCH_FLAGS) + m_flags = maildirUtils::extractFlags(path.getLastComponent()); + + if (options & folder::FETCH_SIZE) + m_size = file->getLength(); + + if (options & folder::FETCH_UID) + m_uid = maildirUtils::extractId(path.getLastComponent()).getBuffer(); + + if (options & (folder::FETCH_ENVELOPE | folder::FETCH_CONTENT_INFO | + folder::FETCH_FULL_HEADER | folder::FETCH_STRUCTURE)) + { + string contents; + + utility::auto_ptr reader = file->getFileReader(); + utility::auto_ptr is = reader->getInputStream(); + + // Need whole message contents for structure + if (options & folder::FETCH_STRUCTURE) + { + utility::stream::value_type buffer[16384]; + + contents.reserve(file->getLength()); + + while (!is->eof()) + { + const utility::stream::size_type read = is->read(buffer, sizeof(buffer)); + contents.append(buffer, read); + } + } + // Need only header + else + { + utility::stream::value_type buffer[1024]; + + contents.reserve(4096); + + while (!is->eof()) + { + const utility::stream::size_type read = is->read(buffer, sizeof(buffer)); + contents.append(buffer, read); + + const string::size_type sep1 = contents.rfind("\r\n\r\n"); + const string::size_type sep2 = contents.rfind("\n\n"); + + if (sep1 != string::npos) + { + contents.erase(contents.begin() + sep1 + 4, contents.end()); + break; + } + else if (sep2 != string::npos) + { + contents.erase(contents.begin() + sep2 + 2, contents.end()); + break; + } + } + } + + vmime::message msg; + msg.parse(contents); + + // Extract structure + if (options & folder::FETCH_STRUCTURE) + { + if (m_structure) + delete (m_structure); + + m_structure = new maildirStructure(NULL, msg); + } + + // Extract some header fields or whole header + if (options & (folder::FETCH_ENVELOPE | + folder::FETCH_CONTENT_INFO | + folder::FETCH_FULL_HEADER)) + { + getOrCreateHeader().copyFrom(*(msg.getHeader())); + } + } +} + + +header& maildirMessage::getOrCreateHeader() +{ + if (m_header != NULL) + return (*m_header); + else + return (*(m_header = new header())); +} + + +} // maildir +} // messaging +} // vmime diff --git a/src/messaging/maildir/maildirStore.cpp b/src/messaging/maildir/maildirStore.cpp new file mode 100644 index 00000000..7188a281 --- /dev/null +++ b/src/messaging/maildir/maildirStore.cpp @@ -0,0 +1,234 @@ +// +// 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/maildir/maildirStore.hpp" + +#include "vmime/messaging/maildir/maildirFolder.hpp" + +#include "vmime/utility/smartPtr.hpp" + +#include "vmime/exception.hpp" +#include "vmime/platformDependant.hpp" + + +namespace vmime { +namespace messaging { +namespace maildir { + + +maildirStore::maildirStore(session* sess, authenticator* auth) + : store(sess, getInfosInstance(), auth), m_connected(false) +{ +} + + +maildirStore::~maildirStore() +{ + if (isConnected()) + disconnect(); +} + + +const string maildirStore::getProtocolName() const +{ + return "maildir"; +} + + +folder* maildirStore::getRootFolder() +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return new maildirFolder(folder::path(), this); +} + + +folder* maildirStore::getDefaultFolder() +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return new maildirFolder(folder::path::component("inbox"), this); +} + + +folder* maildirStore::getFolder(const folder::path& path) +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return new maildirFolder(path, this); +} + + +const bool maildirStore::isValidFolderName(const folder::path::component& name) const +{ + if (!platformDependant::getHandler()->getFileSystemFactory()->isValidPathComponent(name)) + return false; + + const string& buf = name.getBuffer(); + + // Name cannot start/end with spaces + if (utility::stringUtils::trim(buf) != name.getBuffer()) + return false; + + // Name cannot start with '.' + const int length = buf.length(); + int pos = 0; + + while ((pos < length) && (buf[pos] == '.')) + ++pos; + + return (pos == 0); +} + + +void maildirStore::connect() +{ + if (isConnected()) + throw exceptions::already_connected(); + + // Get root directory + utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); + + m_fsPath = fsf->stringToPath + (getSession()->getProperties()[getInfos().getPropertyPrefix() + "server.rootpath"]); + + utility::auto_ptr rootDir = fsf->create(m_fsPath); + + // Try to create the root directory if it does not exist + if (!(rootDir->exists() && rootDir->isDirectory())) + { + try + { + rootDir->createDirectory(); + } + catch (exceptions::filesystem_exception& e) + { + throw exceptions::connection_error("Cannot create root directory.", e); + } + } + + m_connected = true; +} + + +const bool maildirStore::isConnected() const +{ + return (m_connected); +} + + +void maildirStore::disconnect() +{ + for (std::list ::iterator it = m_folders.begin() ; + it != m_folders.end() ; ++it) + { + (*it)->onStoreDisconnected(); + } + + m_folders.clear(); + + m_connected = false; +} + + +void maildirStore::noop() +{ + // Nothing to do. +} + + +void maildirStore::registerFolder(maildirFolder* folder) +{ + m_folders.push_back(folder); +} + + +void maildirStore::unregisterFolder(maildirFolder* folder) +{ + std::list ::iterator it = std::find(m_folders.begin(), m_folders.end(), folder); + if (it != m_folders.end()) m_folders.erase(it); +} + + +const utility::path& maildirStore::getFileSystemPath() const +{ + return (m_fsPath); +} + + +const int maildirStore::getCapabilities() const +{ + return (CAPABILITY_CREATE_FOLDER | + CAPABILITY_RENAME_FOLDER | + CAPABILITY_ADD_MESSAGE | + CAPABILITY_COPY_MESSAGE | + CAPABILITY_DELETE_MESSAGE | + CAPABILITY_PARTIAL_FETCH | + CAPABILITY_MESSAGE_FLAGS | + CAPABILITY_EXTRACT_PART); +} + + + + +// Service infos + +maildirStore::_infos maildirStore::sm_infos; + + +const serviceInfos& maildirStore::getInfosInstance() +{ + return (sm_infos); +} + + +const serviceInfos& maildirStore::getInfos() const +{ + return (sm_infos); +} + + +const port_t maildirStore::_infos::getDefaultPort() const +{ + return (0); +} + + +const string maildirStore::_infos::getPropertyPrefix() const +{ + return "store.maildir."; +} + + +const std::vector maildirStore::_infos::getAvailableProperties() const +{ + std::vector list; + + list.push_back("server.rootpath"); + + return (list); +} + + +} // maildir +} // messaging +} // vmime diff --git a/src/messaging/maildir/maildirUtils.cpp b/src/messaging/maildir/maildirUtils.cpp new file mode 100644 index 00000000..4476b3dd --- /dev/null +++ b/src/messaging/maildir/maildirUtils.cpp @@ -0,0 +1,194 @@ +// +// 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/maildir/maildirUtils.hpp" +#include "vmime/messaging/maildir/maildirStore.hpp" + +#include "vmime/utility/random.hpp" + + +namespace vmime { +namespace messaging { +namespace maildir { + + +const vmime::word maildirUtils::TMP_DIR("tmp", vmime::charset(vmime::charsets::US_ASCII)); // ensure reliable delivery (not to be listed) +const vmime::word maildirUtils::CUR_DIR("cur", vmime::charset(vmime::charsets::US_ASCII)); // no longer new messages +const vmime::word maildirUtils::NEW_DIR("new", vmime::charset(vmime::charsets::US_ASCII)); // unread messages + + +const utility::file::path maildirUtils::getFolderFSPath + (maildirStore* store, const utility::path& folderPath, const FolderFSPathMode mode) +{ + // Root path + utility::file::path path(store->getFileSystemPath()); + + const int count = (mode == FOLDER_PATH_CONTAINER + ? folderPath.getSize() : folderPath.getSize() - 1); + + // Parent folders + for (int i = 0 ; i < count ; ++i) + { + utility::file::path::component comp(folderPath[i]); + + // TODO: may not work with all encodings... + comp.setBuffer("." + comp.getBuffer() + ".directory"); + + path /= comp; + } + + // Last component + if (folderPath.getSize() != 0 && + mode != FOLDER_PATH_CONTAINER) + { + path /= folderPath.getLastComponent(); + + switch (mode) + { + case FOLDER_PATH_ROOT: break; // Nothing to do + case FOLDER_PATH_NEW: path /= NEW_DIR; break; + case FOLDER_PATH_CUR: path /= CUR_DIR; break; + case FOLDER_PATH_TMP: path /= TMP_DIR; break; + case FOLDER_PATH_CONTAINER: break; // Can't happen... + } + } + + return (path); +} + + +const bool maildirUtils::isSubfolderDirectory(const utility::file& file) +{ + // A directory which name does not start with '.' + // is listed as a sub-folder... + if (file.isDirectory() && + file.getFullPath().getLastComponent().getBuffer().length() >= 1 && + file.getFullPath().getLastComponent().getBuffer()[0] != '.') + { + return (true); + } + + return (false); +} + + +const utility::file::path::component maildirUtils::extractId + (const utility::file::path::component& filename) +{ + string::size_type sep = filename.getBuffer().rfind(':'); + if (sep == string::npos) return (filename); + + return (utility::path::component + (string(filename.getBuffer().begin(), filename.getBuffer().begin() + sep))); +} + + +const int maildirUtils::extractFlags(const utility::file::path::component& comp) +{ + string::size_type sep = comp.getBuffer().rfind(':'); + if (sep == string::npos) return (0); + + const string flagsString(comp.getBuffer().begin() + sep + 1, comp.getBuffer().end()); + const string::size_type count = flagsString.length(); + + int flags = 0; + + for (string::size_type i = 0 ; i < count ; ++i) + { + switch (flagsString[i]) + { + case 'R': case 'r': flags |= message::FLAG_REPLIED; break; + case 'S': case 's': flags |= message::FLAG_SEEN; break; + case 'T': case 't': flags |= message::FLAG_DELETED; break; + case 'F': case 'f': flags |= message::FLAG_MARKED; break; + case 'P': case 'p': flags |= message::FLAG_PASSED; break; + } + } + + return (flags); +} + + +const utility::file::path::component maildirUtils::buildFlags(const int flags) +{ + string str; + str.reserve(8); + + str += "2,"; + + if (flags & message::FLAG_MARKED) str += "F"; + if (flags & message::FLAG_PASSED) str += "P"; + if (flags & message::FLAG_REPLIED) str += "R"; + if (flags & message::FLAG_SEEN) str += "S"; + if (flags & message::FLAG_DELETED) str += "T"; + + return (utility::file::path::component(str)); +} + + +const utility::file::path::component maildirUtils::buildFilename + (const utility::file::path::component& id, const int flags) +{ + return (buildFilename(id, buildFlags(flags))); +} + + +const utility::file::path::component maildirUtils::buildFilename + (const utility::file::path::component& id, const utility::file::path::component& flags) +{ + return (utility::path::component(id.getBuffer() + ":" + flags.getBuffer())); +} + + +const utility::file::path::component maildirUtils::generateId() +{ + std::ostringstream oss; + + oss << utility::random::getTime(); + oss << "."; + oss << utility::random::getProcess(); + oss << "."; + oss << utility::random::getString(6); + + return (utility::file::path::component(oss.str())); +} + + + +// +// messageIdComparator +// + +maildirUtils::messageIdComparator::messageIdComparator + (const utility::file::path::component& comp) + : m_comp(maildirUtils::extractId(comp)) +{ +} + + +const bool maildirUtils::messageIdComparator::operator() + (const utility::file::path::component& other) const +{ + return (m_comp == maildirUtils::extractId(other)); +} + + +} // maildir +} // messaging +} // vmime diff --git a/src/messaging/maildirFolder.cpp b/src/messaging/maildirFolder.cpp deleted file mode 100644 index 85e041aa..00000000 --- a/src/messaging/maildirFolder.cpp +++ /dev/null @@ -1,1348 +0,0 @@ -// -// 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/maildirFolder.hpp" - -#include "vmime/messaging/maildirStore.hpp" -#include "vmime/messaging/maildirMessage.hpp" -#include "vmime/messaging/maildirUtils.hpp" - -#include "vmime/utility/smartPtr.hpp" - -#include "vmime/message.hpp" - -#include "vmime/exception.hpp" -#include "vmime/platformDependant.hpp" - - -namespace vmime { -namespace messaging { - - -maildirFolder::maildirFolder(const folder::path& path, maildirStore* store) - : m_store(store), m_path(path), - m_name(path.isEmpty() ? folder::path::component("") : path.getLastComponent()), - m_mode(-1), m_open(false), m_unreadMessageCount(0), m_messageCount(0) -{ - m_store->registerFolder(this); -} - - -maildirFolder::~maildirFolder() -{ - if (m_store) - { - if (m_open) - close(false); - - m_store->unregisterFolder(this); - } - else if (m_open) - { - close(false); - } -} - - -void maildirFolder::onStoreDisconnected() -{ - m_store = NULL; -} - - -const int maildirFolder::getMode() const -{ - if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); - - return (m_mode); -} - - -const int maildirFolder::getType() -{ - if (m_path.isEmpty()) - return (TYPE_CONTAINS_FOLDERS); - else - return (TYPE_CONTAINS_FOLDERS | TYPE_CONTAINS_MESSAGES); -} - - -const int maildirFolder::getFlags() -{ - int flags = 0; - - utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); - - utility::auto_ptr rootDir = fsf->create - (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_CONTAINER)); - - utility::auto_ptr it = rootDir->getFiles(); - - while (it->hasMoreElements()) - { - utility::auto_ptr file = it->nextElement(); - - if (maildirUtils::isSubfolderDirectory(*file)) - { - flags |= FLAG_CHILDREN; // Contains at least one sub-folder - break; - } - } - - return (flags); -} - - -const folder::path::component maildirFolder::getName() const -{ - return (m_name); -} - - -const folder::path maildirFolder::getFullPath() const -{ - return (m_path); -} - - -void maildirFolder::open(const int mode, bool /* failIfModeIsNotAvailable */) -{ - if (!m_store) - throw exceptions::illegal_state("Store disconnected"); - else if (isOpen()) - throw exceptions::illegal_state("Folder is already open"); - else if (!exists()) - throw exceptions::illegal_state("Folder does not exist"); - - scanFolder(); - - m_open = true; - m_mode = mode; -} - - -void maildirFolder::close(const bool expunge) -{ - if (!m_store) - throw exceptions::illegal_state("Store disconnected"); - - if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); - - if (expunge) - this->expunge(); - - m_open = false; - m_mode = -1; - - onClose(); -} - - -void maildirFolder::onClose() -{ - for (std::vector ::iterator it = m_messages.begin() ; - it != m_messages.end() ; ++it) - { - (*it)->onFolderClosed(); - } - - m_messages.clear(); -} - - -void maildirFolder::registerMessage(maildirMessage* msg) -{ - m_messages.push_back(msg); -} - - -void maildirFolder::unregisterMessage(maildirMessage* msg) -{ - std::vector ::iterator it = - std::find(m_messages.begin(), m_messages.end(), msg); - - if (it != m_messages.end()) - m_messages.erase(it); -} - - -void maildirFolder::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(); - - // Create directory on file system - try - { - utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); - - if (!fsf->isValidPath(maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_ROOT))) - throw exceptions::invalid_folder_name(); - - utility::auto_ptr rootDir = fsf->create - (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_ROOT)); - - utility::auto_ptr newDir = fsf->create - (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_NEW)); - utility::auto_ptr tmpDir = fsf->create - (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_TMP)); - utility::auto_ptr curDir = fsf->create - (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_CUR)); - - rootDir->createDirectory(true); - - newDir->createDirectory(false); - tmpDir->createDirectory(false); - curDir->createDirectory(false); - } - catch (exceptions::filesystem_exception& e) - { - throw exceptions::command_error("CREATE", "", "File system exception", e); - } - - // Notify folder created - events::folderEvent event(this, events::folderEvent::TYPE_CREATED, m_path, m_path); - notifyFolder(event); -} - - -const bool maildirFolder::exists() -{ - utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); - - utility::auto_ptr rootDir = fsf->create - (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_ROOT)); - - utility::auto_ptr newDir = fsf->create - (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_NEW)); - utility::auto_ptr tmpDir = fsf->create - (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_TMP)); - utility::auto_ptr curDir = fsf->create - (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_CUR)); - - return (rootDir->exists() && rootDir->isDirectory() && - newDir->exists() && newDir->isDirectory() && - tmpDir->exists() && tmpDir->isDirectory() && - curDir->exists() && curDir->isDirectory()); -} - - -const bool maildirFolder::isOpen() const -{ - return (m_open); -} - - -void maildirFolder::scanFolder() -{ - try - { - m_messageCount = 0; - m_unreadMessageCount = 0; - - utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); - - utility::file::path newDirPath = maildirUtils::getFolderFSPath - (m_store, m_path, maildirUtils::FOLDER_PATH_NEW); - utility::auto_ptr newDir = fsf->create(newDirPath); - - utility::file::path curDirPath = maildirUtils::getFolderFSPath - (m_store, m_path, maildirUtils::FOLDER_PATH_CUR); - utility::auto_ptr curDir = fsf->create(curDirPath); - - // New received messages (new/) - utility::fileIterator* nit = newDir->getFiles(); - std::vector newMessageFilenames; - - while (nit->hasMoreElements()) - { - utility::auto_ptr file = nit->nextElement(); - newMessageFilenames.push_back(file->getFullPath().getLastComponent()); - } - - delete (nit); // Free directory - - // Current messages (cur/) - utility::fileIterator* cit = curDir->getFiles(); - std::vector curMessageFilenames; - - while (cit->hasMoreElements()) - { - utility::auto_ptr file = cit->nextElement(); - curMessageFilenames.push_back(file->getFullPath().getLastComponent()); - } - - delete (cit); // Free directory - - // Update/delete existing messages (found in previous scan) - for (unsigned int i = 0 ; i < m_messageInfos.size() ; ++i) - { - messageInfos& msgInfos = m_messageInfos[i]; - - // NOTE: the flags may have changed (eg. moving from 'new' to 'cur' - // may imply the 'S' flag) and so the filename. That's why we use - // "maildirUtils::messageIdComparator" to compare only the 'unique' - // portion of the filename... - - if (msgInfos.type == messageInfos::TYPE_CUR) - { - const std::vector ::iterator pos = - std::find_if(curMessageFilenames.begin(), curMessageFilenames.end(), - maildirUtils::messageIdComparator(msgInfos.path)); - - // If we cannot find this message in the 'cur' directory, - // it means it has been deleted (and expunged). - if (pos == curMessageFilenames.end()) - { - msgInfos.type = messageInfos::TYPE_DELETED; - } - // Otherwise, update its information. - else - { - msgInfos.path = *pos; - curMessageFilenames.erase(pos); - } - } - } - - m_messageInfos.reserve(m_messageInfos.size() - + newMessageFilenames.size() + curMessageFilenames.size()); - - // Add new messages from 'new': we are responsible to move the files - // from the 'new' directory to the 'cur' directory, and append them - // to our message list. - for (std::vector ::const_iterator - it = newMessageFilenames.begin() ; it != newMessageFilenames.end() ; ++it) - { - const utility::file::path::component newFilename = - maildirUtils::buildFilename(maildirUtils::extractId(*it), 0); - - // Move messages from 'new' to 'cur' - utility::auto_ptr file = fsf->create(newDirPath / *it); - file->rename(curDirPath / newFilename); - - // Append to message list - messageInfos msgInfos; - msgInfos.path = newFilename; - msgInfos.type = messageInfos::TYPE_CUR; - - m_messageInfos.push_back(msgInfos); - } - - // Add new messages from 'cur': the files have already been moved - // from 'new' to 'cur'. Just append them to our message list. - for (std::vector ::const_iterator - it = curMessageFilenames.begin() ; it != curMessageFilenames.end() ; ++it) - { - // Append to message list - messageInfos msgInfos; - msgInfos.path = *it; - msgInfos.type = messageInfos::TYPE_CUR; - - m_messageInfos.push_back(msgInfos); - } - - // Update message count - int unreadMessageCount = 0; - - for (std::vector ::const_iterator - it = m_messageInfos.begin() ; it != m_messageInfos.end() ; ++it) - { - if ((maildirUtils::extractFlags((*it).path) & message::FLAG_SEEN) == 0) - ++unreadMessageCount; - } - - m_unreadMessageCount = unreadMessageCount; - m_messageCount = m_messageInfos.size(); - } - catch (exceptions::filesystem_exception&) - { - // Should not happen... - } -} - - -message* maildirFolder::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 maildirMessage(this, num); -} - - -std::vector maildirFolder::getMessages(const int from, const int to) -{ - if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); - - std::vector v; - - for (int i = from ; i <= to ; ++i) - v.push_back(new maildirMessage(this, i)); - - return (v); -} - - -std::vector maildirFolder::getMessages(const std::vector & nums) -{ - if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); - - std::vector v; - - for (std::vector ::const_iterator it = nums.begin() ; it != nums.end() ; ++it) - v.push_back(new maildirMessage(this, *it)); - - return (v); -} - - -const int maildirFolder::getMessageCount() -{ - return (m_messageCount); -} - - -folder* maildirFolder::getFolder(const folder::path::component& name) -{ - if (!m_store) - throw exceptions::illegal_state("Store disconnected"); - - return new maildirFolder(m_path / name, m_store); -} - - -std::vector maildirFolder::getFolders(const bool recursive) -{ - if (!isOpen() && !m_store) - throw exceptions::illegal_state("Store disconnected"); - - std::vector list; - - try - { - listFolders(list, recursive); - } - catch (std::exception&) - { - for (std::vector ::iterator it = list.begin() ; it != list.end() ; ++it) - delete (*it); - - throw; - } - catch (vmime::exception&) - { - for (std::vector ::iterator it = list.begin() ; it != list.end() ; ++it) - delete (*it); - - throw; - } - - return (list); -} - - -void maildirFolder::listFolders(std::vector & list, const bool recursive) -{ - try - { - utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); - - utility::auto_ptr rootDir = fsf->create - (maildirUtils::getFolderFSPath(m_store, m_path, - m_path.isEmpty() ? maildirUtils::FOLDER_PATH_ROOT - : maildirUtils::FOLDER_PATH_CONTAINER)); - - if (rootDir->exists()) - { - utility::auto_ptr it = rootDir->getFiles(); - - while (it->hasMoreElements()) - { - utility::auto_ptr file = it->nextElement(); - - if (maildirUtils::isSubfolderDirectory(*file)) - { - const utility::path subPath = m_path / file->getFullPath().getLastComponent(); - maildirFolder* subFolder = new maildirFolder(subPath, m_store); - - list.push_back(subFolder); - - if (recursive) - subFolder->listFolders(list, true); - } - } - } - else - { - // No sub-folder - } - } - catch (exceptions::filesystem_exception& e) - { - throw exceptions::command_error("LIST", "", "", e); - } -} - - -void maildirFolder::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_store->isValidFolderName(newPath.getLastComponent())) - throw exceptions::invalid_folder_name(); - - // Rename the directory on the file system - utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); - - utility::auto_ptr rootDir = fsf->create - (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_ROOT)); - utility::auto_ptr contDir = fsf->create - (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_CONTAINER)); - - try - { - const utility::file::path newRootPath = - maildirUtils::getFolderFSPath(m_store, newPath, maildirUtils::FOLDER_PATH_ROOT); - const utility::file::path newContPath = - maildirUtils::getFolderFSPath(m_store, newPath, maildirUtils::FOLDER_PATH_CONTAINER); - - rootDir->rename(newRootPath); - - // Container directory may not exist, so ignore error when trying to rename it - try - { - contDir->rename(newContPath); - } - catch (exceptions::filesystem_exception& e) - { - // Ignore - } - } - catch (exceptions::filesystem_exception& e) - { - // Revert to old location - const utility::file::path rootPath = - maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_ROOT); - const utility::file::path contPath = - maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_CONTAINER); - - try - { - rootDir->rename(rootPath); - contDir->rename(contPath); - } - catch (exceptions::filesystem_exception& e) - { - // Ignore - } - - throw exceptions::command_error("RENAME", "", "", e); - } - - // 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 - for (std::list ::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 maildirFolder::deleteMessage(const int num) -{ - // Mark messages as deleted - setMessageFlags(num, num, message::FLAG_MODE_ADD, message::FLAG_DELETED); -} - - -void maildirFolder::deleteMessages(const int from, const int to) -{ - // Mark messages as deleted - setMessageFlags(from, to, message::FLAG_MODE_ADD, message::FLAG_DELETED); -} - - -void maildirFolder::deleteMessages(const std::vector & nums) -{ - // Mark messages as deleted - setMessageFlags(nums, message::FLAG_MODE_ADD, message::FLAG_DELETED); -} - - -void maildirFolder::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"); - - // Construct the list of message numbers - const int to2 = (to == -1) ? m_messageCount : to; - const int count = to - from + 1; - - std::vector nums; - nums.resize(count); - - for (int i = from, j = 0 ; i <= to2 ; ++i, ++j) - nums[j] = i; - - // Change message flags - setMessageFlagsImpl(nums, flags, mode); - - // Update local flags - switch (mode) - { - case message::FLAG_MODE_ADD: - { - for (std::vector ::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 ::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 ::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 - events::messageChangedEvent event(this, events::messageChangedEvent::TYPE_FLAGS, nums); - - notifyMessageChanged(event); -} - - -void maildirFolder::setMessageFlags - (const std::vector & 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 list; - - list.resize(nums.size()); - std::copy(nums.begin(), nums.end(), list.begin()); - - std::sort(list.begin(), list.end()); - - // Change message flags - setMessageFlagsImpl(list, flags, mode); - - // Update local flags - switch (mode) - { - case message::FLAG_MODE_ADD: - { - for (std::vector ::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 ::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 ::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 maildirFolder::setMessageFlagsImpl - (const std::vector & nums, const int flags, const int mode) -{ - utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); - - utility::file::path curDirPath = maildirUtils::getFolderFSPath - (m_store, m_path, maildirUtils::FOLDER_PATH_CUR); - - for (std::vector ::const_iterator it = - nums.begin() ; it != nums.end() ; ++it) - { - const int num = *it - 1; - - try - { - const utility::file::path::component path = m_messageInfos[num].path; - utility::auto_ptr file = fsf->create(curDirPath / path); - - int newFlags = maildirUtils::extractFlags(path); - - switch (mode) - { - case message::FLAG_MODE_ADD: newFlags |= flags; break; - case message::FLAG_MODE_REMOVE: newFlags &= ~flags; break; - default: - case message::FLAG_MODE_SET: newFlags = flags; break; - } - - const utility::file::path::component newPath = maildirUtils::buildFilename - (maildirUtils::extractId(path), newFlags); - - file->rename(curDirPath / newPath); - - if (flags & message::FLAG_DELETED) - m_messageInfos[num].type = messageInfos::TYPE_DELETED; - else - m_messageInfos[num].type = messageInfos::TYPE_CUR; - - m_messageInfos[num].path = newPath; - } - catch (exceptions::filesystem_exception& e) - { - // Ignore (not important) - } - } -} - - -void maildirFolder::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 maildirFolder::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"); - - utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); - - utility::file::path tmpDirPath = maildirUtils::getFolderFSPath - (m_store, m_path, maildirUtils::FOLDER_PATH_TMP); - utility::file::path curDirPath = maildirUtils::getFolderFSPath - (m_store, m_path, maildirUtils::FOLDER_PATH_CUR); - - const utility::file::path::component filename = - maildirUtils::buildFilename(maildirUtils::generateId(), - ((flags == message::FLAG_UNDEFINED) ? 0 : flags)); - - try - { - utility::auto_ptr tmpDir = fsf->create(tmpDirPath); - tmpDir->createDirectory(true); - } - catch (exceptions::filesystem_exception&) - { - // Don't throw now, it will fail later... - } - - try - { - utility::auto_ptr curDir = fsf->create(curDirPath); - curDir->createDirectory(true); - } - catch (exceptions::filesystem_exception&) - { - // Don't throw now, it will fail later... - } - - // Actually add the message - copyMessageImpl(tmpDirPath, curDirPath, filename, is, size, progress); - - // Append the message to the cache list - messageInfos msgInfos; - msgInfos.path = filename; - msgInfos.type = messageInfos::TYPE_CUR; - - m_messageInfos.push_back(msgInfos); - m_messageCount++; - - if ((flags == message::FLAG_UNDEFINED) || !(flags & message::FLAG_SEEN)) - m_unreadMessageCount++; - - // Notification - std::vector nums; - nums.push_back(m_messageCount); - - events::messageCountEvent event(this, events::messageCountEvent::TYPE_ADDED, nums); - - notifyMessageCount(event); - - // Notify folders with the same path - for (std::list ::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; - (*it)->m_unreadMessageCount = m_unreadMessageCount; - - (*it)->m_messageInfos.resize(m_messageInfos.size()); - std::copy(m_messageInfos.begin(), m_messageInfos.end(), (*it)->m_messageInfos.begin()); - - events::messageCountEvent event(*it, events::messageCountEvent::TYPE_ADDED, nums); - - (*it)->notifyMessageCount(event); - } - } -} - - -void maildirFolder::copyMessageImpl(const utility::file::path& tmpDirPath, - const utility::file::path& curDirPath, const utility::file::path::component& filename, - utility::inputStream& is, const utility::stream::size_type size, progressionListener* progress) -{ - utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); - - utility::auto_ptr file = fsf->create(tmpDirPath / filename); - - if (progress) - progress->start(size); - - // First, write the message into 'tmp'... - try - { - file->createFile(); - - utility::auto_ptr fw = file->getFileWriter(); - utility::auto_ptr os = fw->getOutputStream(); - - utility::stream::value_type buffer[65536]; - utility::stream::size_type total = 0; - - while (!is.eof()) - { - const utility::stream::size_type read = is.read(buffer, sizeof(buffer)); - - if (read != 0) - { - os->write(buffer, read); - total += read; - } - - if (progress) - progress->progress(total, size); - } - } - catch (exception& e) - { - if (progress) - progress->stop(size); - - // Delete temporary file - try - { - utility::auto_ptr file = fsf->create(tmpDirPath / filename); - file->remove(); - } - catch (exceptions::filesystem_exception&) - { - // Ignore - } - - throw exceptions::command_error("ADD", "", "", e); - } - - // ...then, move it to 'cur' - try - { - file->rename(curDirPath / filename); - } - catch (exception& e) - { - if (progress) - progress->stop(size); - - // Delete temporary file - try - { - utility::auto_ptr file = fsf->create(tmpDirPath / filename); - file->remove(); - } - catch (exceptions::filesystem_exception&) - { - // Ignore - } - - throw exceptions::command_error("ADD", "", "", e); - } - - if (progress) - progress->stop(size); -} - - -void maildirFolder::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"); - - copyMessages(dest, num, num); -} - - -void maildirFolder::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 the list of message numbers - const int to2 = (to == -1) ? m_messageCount : to; - const int count = to - from + 1; - - std::vector nums; - nums.resize(count); - - for (int i = from, j = 0 ; i <= to2 ; ++i, ++j) - nums[j] = i; - - // Copy messages - copyMessagesImpl(dest, nums); -} - - -void maildirFolder::copyMessages(const folder::path& dest, const std::vector & nums) -{ - if (!m_store) - throw exceptions::illegal_state("Store disconnected"); - else if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); - - // Copy messages - copyMessagesImpl(dest, nums); -} - - -void maildirFolder::copyMessagesImpl(const folder::path& dest, const std::vector & nums) -{ - utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); - - utility::file::path curDirPath = maildirUtils::getFolderFSPath - (m_store, m_path, maildirUtils::FOLDER_PATH_CUR); - - utility::file::path destCurDirPath = maildirUtils::getFolderFSPath - (m_store, dest, maildirUtils::FOLDER_PATH_CUR); - utility::file::path destTmpDirPath = maildirUtils::getFolderFSPath - (m_store, dest, maildirUtils::FOLDER_PATH_TMP); - - // Create destination directories - try - { - utility::auto_ptr destTmpDir = fsf->create(destTmpDirPath); - destTmpDir->createDirectory(true); - } - catch (exceptions::filesystem_exception&) - { - // Don't throw now, it will fail later... - } - - try - { - utility::auto_ptr destCurDir = fsf->create(destCurDirPath); - destCurDir->createDirectory(true); - } - catch (exceptions::filesystem_exception&) - { - // Don't throw now, it will fail later... - } - - // Copy messages - try - { - for (std::vector ::const_iterator it = - nums.begin() ; it != nums.end() ; ++it) - { - const int num = *it; - const messageInfos& msg = m_messageInfos[num - 1]; - const int flags = maildirUtils::extractFlags(msg.path); - - const utility::file::path::component filename = - maildirUtils::buildFilename(maildirUtils::generateId(), flags); - - utility::auto_ptr file = fsf->create(curDirPath / msg.path); - utility::auto_ptr fr = file->getFileReader(); - utility::auto_ptr is = fr->getInputStream(); - - copyMessageImpl(destTmpDirPath, destCurDirPath, - filename, *is, file->getLength(), NULL); - } - } - catch (exception& e) - { - notifyMessagesCopied(dest); - throw exceptions::command_error("COPY", "", "", e); - } - - notifyMessagesCopied(dest); -} - - -void maildirFolder::notifyMessagesCopied(const folder::path& dest) -{ - for (std::list ::iterator it = m_store->m_folders.begin() ; - it != m_store->m_folders.end() ; ++it) - { - if ((*it) != this && (*it)->getFullPath() == dest) - { - // We only need to update the first folder we found as calling - // status() will notify all the folders with the same path. - int count, unseen; - (*it)->status(count, unseen); - - return; - } - } -} - - -void maildirFolder::status(int& count, int& unseen) -{ - const int oldCount = m_messageCount; - - scanFolder(); - - count = m_messageCount; - unseen = m_unreadMessageCount; - - // Notify message count changed (new messages) - if (count > oldCount) - { - std::vector 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 ::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; - (*it)->m_unreadMessageCount = m_unreadMessageCount; - - (*it)->m_messageInfos.resize(m_messageInfos.size()); - std::copy(m_messageInfos.begin(), m_messageInfos.end(), (*it)->m_messageInfos.begin()); - - events::messageCountEvent event(*it, events::messageCountEvent::TYPE_ADDED, nums); - - (*it)->notifyMessageCount(event); - } - } - } -} - - -void maildirFolder::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"); - - utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); - - utility::file::path curDirPath = maildirUtils::getFolderFSPath - (m_store, m_path, maildirUtils::FOLDER_PATH_CUR); - - std::vector nums; - int unreadCount = 0; - - for (int num = 1 ; num <= m_messageCount ; ++num) - { - messageInfos& infos = m_messageInfos[num - 1]; - - if (infos.type == messageInfos::TYPE_DELETED) - { - nums.push_back(num); - - for (std::vector ::iterator it = - m_messages.begin() ; it != m_messages.end() ; ++it) - { - if ((*it)->m_num == num) - (*it)->m_expunged = true; - else if ((*it)->m_num > num) - (*it)->m_num--; - } - - if (maildirUtils::extractFlags(infos.path) & message::FLAG_SEEN) - ++unreadCount; - - // Delete file from file system - try - { - utility::auto_ptr file = fsf->create(curDirPath / infos.path); - file->remove(); - } - catch (exceptions::filesystem_exception& e) - { - // Ignore (not important) - } - } - } - - if (!nums.empty()) - { - for (int i = nums.size() - 1 ; i >= 0 ; --i) - m_messageInfos.erase(m_messageInfos.begin() + i); - } - - m_messageCount -= nums.size(); - m_unreadMessageCount -= unreadCount; - - // Notify message expunged - events::messageCountEvent event(this, events::messageCountEvent::TYPE_REMOVED, nums); - - notifyMessageCount(event); - - // Notify folders with the same path - for (std::list ::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; - (*it)->m_unreadMessageCount = m_unreadMessageCount; - - (*it)->m_messageInfos.resize(m_messageInfos.size()); - std::copy(m_messageInfos.begin(), m_messageInfos.end(), (*it)->m_messageInfos.begin()); - - events::messageCountEvent event(*it, events::messageCountEvent::TYPE_REMOVED, nums); - - (*it)->notifyMessageCount(event); - } - } -} - - -folder* maildirFolder::getParent() -{ - return (m_path.isEmpty() ? NULL : new maildirFolder(m_path.getParent(), m_store)); -} - - -const store* maildirFolder::getStore() const -{ - return (m_store); -} - - -store* maildirFolder::getStore() -{ - return (m_store); -} - - -void maildirFolder::fetchMessages(std::vector & 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 ::iterator it = msg.begin() ; - it != msg.end() ; ++it) - { - dynamic_cast (*it)->fetch(this, options); - - if (progress) - progress->progress(++current, total); - } - - if (progress) - progress->stop(total); -} - - -void maildirFolder::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 (msg)->fetch(this, options); -} - - -const int maildirFolder::getFetchCapabilities() const -{ - return (FETCH_ENVELOPE | FETCH_STRUCTURE | FETCH_CONTENT_INFO | - FETCH_FLAGS | FETCH_SIZE | FETCH_FULL_HEADER | FETCH_UID); -} - - -const utility::file::path maildirFolder::getMessageFSPath(const int number) -{ - utility::file::path curDirPath = maildirUtils::getFolderFSPath - (m_store, m_path, maildirUtils::FOLDER_PATH_CUR); - - return (curDirPath / m_messageInfos[number - 1].path); -} - - -} // messaging -} // vmime diff --git a/src/messaging/maildirMessage.cpp b/src/messaging/maildirMessage.cpp deleted file mode 100644 index 0a4c27e8..00000000 --- a/src/messaging/maildirMessage.cpp +++ /dev/null @@ -1,503 +0,0 @@ -// -// 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/maildirMessage.hpp" -#include "vmime/messaging/maildirFolder.hpp" -#include "vmime/messaging/maildirUtils.hpp" - -#include "vmime/messageParser.hpp" // to include "message.hpp" in root directory - -#include "vmime/exception.hpp" -#include "vmime/platformDependant.hpp" - - -namespace vmime { -namespace messaging { - - -// -// maildirPart -// - -class maildirStructure; - -class maildirPart : public part -{ -public: - - maildirPart(maildirPart* parent, const int number, const bodyPart& part); - ~maildirPart(); - - - const structure& getStructure() const; - structure& getStructure(); - - const maildirPart* 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); - } - - header& getOrCreateHeader() - { - if (m_header != NULL) - return (*m_header); - else - return (*(m_header = new header())); - } - - const int getHeaderParsedOffset() const { return (m_headerParsedOffset); } - const int getHeaderParsedLength() const { return (m_headerParsedLength); } - - const int getBodyParsedOffset() const { return (m_bodyParsedOffset); } - const int getBodyParsedLength() const { return (m_bodyParsedLength); } - -private: - - maildirStructure* m_structure; - maildirPart* m_parent; - header* m_header; - - int m_number; - int m_size; - mediaType m_mediaType; - - int m_headerParsedOffset; - int m_headerParsedLength; - - int m_bodyParsedOffset; - int m_bodyParsedLength; -}; - - - -// -// maildirStructure -// - -class maildirStructure : public structure -{ -private: - - maildirStructure() - { - } - -public: - - maildirStructure(maildirPart* parent, const bodyPart& part) - { - m_parts.push_back(new maildirPart(parent, 1, part)); - } - - maildirStructure(maildirPart* parent, const std::vector & list) - { - int number = 1; - - for (unsigned int i = 0 ; i < list.size() ; ++i) - m_parts.push_back(new maildirPart(parent, number, *list[i])); - } - - ~maildirStructure() - { - 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 maildirStructure* emptyStructure() - { - return (&m_emptyStructure); - } - -private: - - static maildirStructure m_emptyStructure; - - std::vector m_parts; -}; - - -maildirStructure maildirStructure::m_emptyStructure; - - - -maildirPart::maildirPart(maildirPart* parent, const int number, const bodyPart& part) - : m_parent(parent), m_header(NULL), m_number(number) -{ - if (part.getBody()->getPartList().size() == 0) - m_structure = NULL; - else - m_structure = new maildirStructure(this, part.getBody()->getPartList()); - - m_headerParsedOffset = part.getHeader()->getParsedOffset(); - m_headerParsedLength = part.getHeader()->getParsedLength(); - - m_bodyParsedOffset = part.getBody()->getParsedOffset(); - m_bodyParsedLength = part.getBody()->getParsedLength(); - - m_size = part.getBody()->getContents().getLength(); - - m_mediaType = part.getBody()->getContentType(); -} - - -maildirPart::~maildirPart() -{ - delete (m_structure); - delete (m_header); -} - - -const structure& maildirPart::getStructure() const -{ - if (m_structure != NULL) - return (*m_structure); - else - return (*maildirStructure::emptyStructure()); -} - - -structure& maildirPart::getStructure() -{ - if (m_structure != NULL) - return (*m_structure); - else - return (*maildirStructure::emptyStructure()); -} - - - -// -// maildirMessage -// - -maildirMessage::maildirMessage(maildirFolder* 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); -} - - -maildirMessage::~maildirMessage() -{ - if (m_folder) - m_folder->unregisterMessage(this); - - delete (m_header); - delete (m_structure); -} - - -void maildirMessage::onFolderClosed() -{ - m_folder = NULL; -} - - -const int maildirMessage::getNumber() const -{ - return (m_num); -} - - -const message::uid maildirMessage::getUniqueId() const -{ - return (m_uid); -} - - -const int maildirMessage::getSize() const -{ - if (m_size == -1) - throw exceptions::unfetched_object(); - - return (m_size); -} - - -const bool maildirMessage::isExpunged() const -{ - return (m_expunged); -} - - -const structure& maildirMessage::getStructure() const -{ - if (m_structure == NULL) - throw exceptions::unfetched_object(); - - return (*m_structure); -} - - -structure& maildirMessage::getStructure() -{ - if (m_structure == NULL) - throw exceptions::unfetched_object(); - - return (*m_structure); -} - - -const header& maildirMessage::getHeader() const -{ - if (m_header == NULL) - throw exceptions::unfetched_object(); - - return (*m_header); -} - - -const int maildirMessage::getFlags() const -{ - if (m_flags == FLAG_UNDEFINED) - throw exceptions::unfetched_object(); - - return (m_flags); -} - - -void maildirMessage::setFlags(const int flags, const int mode) -{ - if (!m_folder) - throw exceptions::folder_not_found(); - - m_folder->setMessageFlags(m_num, m_num, flags, mode); -} - - -void maildirMessage::extract(utility::outputStream& os, - progressionListener* progress, const int start, const int length) const -{ - extractImpl(os, progress, 0, m_size, start, length); -} - - -void maildirMessage::extractPart(const part& p, utility::outputStream& os, - progressionListener* progress, const int start, const int length) const -{ - const maildirPart& mp = dynamic_cast (p); - - extractImpl(os, progress, mp.getBodyParsedOffset(), mp.getBodyParsedLength(), start, length); -} - - -void maildirMessage::extractImpl(utility::outputStream& os, progressionListener* progress, - const int start, const int length, const int partialStart, const int partialLength) const -{ - utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); - - const utility::file::path path = m_folder->getMessageFSPath(m_num); - utility::auto_ptr file = fsf->create(path); - - utility::auto_ptr reader = file->getFileReader(); - utility::auto_ptr is = reader->getInputStream(); - - is->skip(start + partialStart); - - utility::stream::value_type buffer[8192]; - utility::stream::size_type remaining = (partialLength == -1 ? length - : std::min(partialLength, length)); - - const int total = remaining; - int current = 0; - - if (progress) - progress->start(total); - - while (!is->eof() && remaining > 0) - { - const utility::stream::size_type read = - is->read(buffer, std::min(remaining, sizeof(buffer))); - - remaining -= read; - current += read; - - os.write(buffer, read); - - if (progress) - progress->progress(current, total); - } - - if (progress) - progress->stop(total); -} - - -void maildirMessage::fetchPartHeader(part& p) -{ - maildirPart& mp = dynamic_cast (p); - - utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); - - const utility::file::path path = m_folder->getMessageFSPath(m_num); - utility::auto_ptr file = fsf->create(path); - - utility::auto_ptr reader = file->getFileReader(); - utility::auto_ptr is = reader->getInputStream(); - - is->skip(mp.getHeaderParsedOffset()); - - utility::stream::value_type buffer[1024]; - utility::stream::size_type remaining = mp.getHeaderParsedLength(); - - string contents; - contents.reserve(remaining); - - while (!is->eof() && remaining > 0) - { - const utility::stream::size_type read = - is->read(buffer, std::min(remaining, sizeof(buffer))); - - remaining -= read; - - contents.append(buffer, read); - } - - mp.getOrCreateHeader().parse(contents); -} - - -void maildirMessage::fetch(maildirFolder* folder, const int options) -{ - if (m_folder != folder) - throw exceptions::folder_not_found(); - - utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); - - const utility::file::path path = folder->getMessageFSPath(m_num); - utility::auto_ptr file = fsf->create(path); - - if (options & folder::FETCH_FLAGS) - m_flags = maildirUtils::extractFlags(path.getLastComponent()); - - if (options & folder::FETCH_SIZE) - m_size = file->getLength(); - - if (options & folder::FETCH_UID) - m_uid = maildirUtils::extractId(path.getLastComponent()).getBuffer(); - - if (options & (folder::FETCH_ENVELOPE | folder::FETCH_CONTENT_INFO | - folder::FETCH_FULL_HEADER | folder::FETCH_STRUCTURE)) - { - string contents; - - utility::auto_ptr reader = file->getFileReader(); - utility::auto_ptr is = reader->getInputStream(); - - // Need whole message contents for structure - if (options & folder::FETCH_STRUCTURE) - { - utility::stream::value_type buffer[16384]; - - contents.reserve(file->getLength()); - - while (!is->eof()) - { - const utility::stream::size_type read = is->read(buffer, sizeof(buffer)); - contents.append(buffer, read); - } - } - // Need only header - else - { - utility::stream::value_type buffer[1024]; - - contents.reserve(4096); - - while (!is->eof()) - { - const utility::stream::size_type read = is->read(buffer, sizeof(buffer)); - contents.append(buffer, read); - - const string::size_type sep1 = contents.rfind("\r\n\r\n"); - const string::size_type sep2 = contents.rfind("\n\n"); - - if (sep1 != string::npos) - { - contents.erase(contents.begin() + sep1 + 4, contents.end()); - break; - } - else if (sep2 != string::npos) - { - contents.erase(contents.begin() + sep2 + 2, contents.end()); - break; - } - } - } - - vmime::message msg; - msg.parse(contents); - - // Extract structure - if (options & folder::FETCH_STRUCTURE) - { - if (m_structure) - delete (m_structure); - - m_structure = new maildirStructure(NULL, msg); - } - - // Extract some header fields or whole header - if (options & (folder::FETCH_ENVELOPE | - folder::FETCH_CONTENT_INFO | - folder::FETCH_FULL_HEADER)) - { - getOrCreateHeader().copyFrom(*(msg.getHeader())); - } - } -} - - -header& maildirMessage::getOrCreateHeader() -{ - if (m_header != NULL) - return (*m_header); - else - return (*(m_header = new header())); -} - - -} // messaging -} // vmime diff --git a/src/messaging/maildirStore.cpp b/src/messaging/maildirStore.cpp deleted file mode 100644 index 7d8e493d..00000000 --- a/src/messaging/maildirStore.cpp +++ /dev/null @@ -1,232 +0,0 @@ -// -// 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/maildirStore.hpp" - -#include "vmime/messaging/maildirFolder.hpp" - -#include "vmime/utility/smartPtr.hpp" - -#include "vmime/exception.hpp" -#include "vmime/platformDependant.hpp" - - -namespace vmime { -namespace messaging { - - -maildirStore::maildirStore(session* sess, authenticator* auth) - : store(sess, getInfosInstance(), auth), m_connected(false) -{ -} - - -maildirStore::~maildirStore() -{ - if (isConnected()) - disconnect(); -} - - -const string maildirStore::getProtocolName() const -{ - return "maildir"; -} - - -folder* maildirStore::getRootFolder() -{ - if (!isConnected()) - throw exceptions::illegal_state("Not connected"); - - return new maildirFolder(folder::path(), this); -} - - -folder* maildirStore::getDefaultFolder() -{ - if (!isConnected()) - throw exceptions::illegal_state("Not connected"); - - return new maildirFolder(folder::path::component("inbox"), this); -} - - -folder* maildirStore::getFolder(const folder::path& path) -{ - if (!isConnected()) - throw exceptions::illegal_state("Not connected"); - - return new maildirFolder(path, this); -} - - -const bool maildirStore::isValidFolderName(const folder::path::component& name) const -{ - if (!platformDependant::getHandler()->getFileSystemFactory()->isValidPathComponent(name)) - return false; - - const string& buf = name.getBuffer(); - - // Name cannot start/end with spaces - if (utility::stringUtils::trim(buf) != name.getBuffer()) - return false; - - // Name cannot start with '.' - const int length = buf.length(); - int pos = 0; - - while ((pos < length) && (buf[pos] == '.')) - ++pos; - - return (pos == 0); -} - - -void maildirStore::connect() -{ - if (isConnected()) - throw exceptions::already_connected(); - - // Get root directory - utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); - - m_fsPath = fsf->stringToPath - (getSession()->getProperties()[getInfos().getPropertyPrefix() + "server.rootpath"]); - - utility::auto_ptr rootDir = fsf->create(m_fsPath); - - // Try to create the root directory if it does not exist - if (!(rootDir->exists() && rootDir->isDirectory())) - { - try - { - rootDir->createDirectory(); - } - catch (exceptions::filesystem_exception& e) - { - throw exceptions::connection_error("Cannot create root directory.", e); - } - } - - m_connected = true; -} - - -const bool maildirStore::isConnected() const -{ - return (m_connected); -} - - -void maildirStore::disconnect() -{ - for (std::list ::iterator it = m_folders.begin() ; - it != m_folders.end() ; ++it) - { - (*it)->onStoreDisconnected(); - } - - m_folders.clear(); - - m_connected = false; -} - - -void maildirStore::noop() -{ - // Nothing to do. -} - - -void maildirStore::registerFolder(maildirFolder* folder) -{ - m_folders.push_back(folder); -} - - -void maildirStore::unregisterFolder(maildirFolder* folder) -{ - std::list ::iterator it = std::find(m_folders.begin(), m_folders.end(), folder); - if (it != m_folders.end()) m_folders.erase(it); -} - - -const utility::path& maildirStore::getFileSystemPath() const -{ - return (m_fsPath); -} - - -const int maildirStore::getCapabilities() const -{ - return (CAPABILITY_CREATE_FOLDER | - CAPABILITY_RENAME_FOLDER | - CAPABILITY_ADD_MESSAGE | - CAPABILITY_COPY_MESSAGE | - CAPABILITY_DELETE_MESSAGE | - CAPABILITY_PARTIAL_FETCH | - CAPABILITY_MESSAGE_FLAGS | - CAPABILITY_EXTRACT_PART); -} - - - - -// Service infos - -maildirStore::_infos maildirStore::sm_infos; - - -const serviceInfos& maildirStore::getInfosInstance() -{ - return (sm_infos); -} - - -const serviceInfos& maildirStore::getInfos() const -{ - return (sm_infos); -} - - -const port_t maildirStore::_infos::getDefaultPort() const -{ - return (0); -} - - -const string maildirStore::_infos::getPropertyPrefix() const -{ - return "store.maildir."; -} - - -const std::vector maildirStore::_infos::getAvailableProperties() const -{ - std::vector list; - - list.push_back("server.rootpath"); - - return (list); -} - - -} // messaging -} // vmime diff --git a/src/messaging/maildirUtils.cpp b/src/messaging/maildirUtils.cpp deleted file mode 100644 index 3479bdeb..00000000 --- a/src/messaging/maildirUtils.cpp +++ /dev/null @@ -1,192 +0,0 @@ -// -// 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/maildirUtils.hpp" -#include "vmime/messaging/maildirStore.hpp" - -#include "vmime/utility/random.hpp" - - -namespace vmime { -namespace messaging { - - -const vmime::word maildirUtils::TMP_DIR("tmp", vmime::charset(vmime::charsets::US_ASCII)); // ensure reliable delivery (not to be listed) -const vmime::word maildirUtils::CUR_DIR("cur", vmime::charset(vmime::charsets::US_ASCII)); // no longer new messages -const vmime::word maildirUtils::NEW_DIR("new", vmime::charset(vmime::charsets::US_ASCII)); // unread messages - - -const utility::file::path maildirUtils::getFolderFSPath - (maildirStore* store, const utility::path& folderPath, const FolderFSPathMode mode) -{ - // Root path - utility::file::path path(store->getFileSystemPath()); - - const int count = (mode == FOLDER_PATH_CONTAINER - ? folderPath.getSize() : folderPath.getSize() - 1); - - // Parent folders - for (int i = 0 ; i < count ; ++i) - { - utility::file::path::component comp(folderPath[i]); - - // TODO: may not work with all encodings... - comp.setBuffer("." + comp.getBuffer() + ".directory"); - - path /= comp; - } - - // Last component - if (folderPath.getSize() != 0 && - mode != FOLDER_PATH_CONTAINER) - { - path /= folderPath.getLastComponent(); - - switch (mode) - { - case FOLDER_PATH_ROOT: break; // Nothing to do - case FOLDER_PATH_NEW: path /= NEW_DIR; break; - case FOLDER_PATH_CUR: path /= CUR_DIR; break; - case FOLDER_PATH_TMP: path /= TMP_DIR; break; - case FOLDER_PATH_CONTAINER: break; // Can't happen... - } - } - - return (path); -} - - -const bool maildirUtils::isSubfolderDirectory(const utility::file& file) -{ - // A directory which name does not start with '.' - // is listed as a sub-folder... - if (file.isDirectory() && - file.getFullPath().getLastComponent().getBuffer().length() >= 1 && - file.getFullPath().getLastComponent().getBuffer()[0] != '.') - { - return (true); - } - - return (false); -} - - -const utility::file::path::component maildirUtils::extractId - (const utility::file::path::component& filename) -{ - string::size_type sep = filename.getBuffer().rfind(':'); - if (sep == string::npos) return (filename); - - return (utility::path::component - (string(filename.getBuffer().begin(), filename.getBuffer().begin() + sep))); -} - - -const int maildirUtils::extractFlags(const utility::file::path::component& comp) -{ - string::size_type sep = comp.getBuffer().rfind(':'); - if (sep == string::npos) return (0); - - const string flagsString(comp.getBuffer().begin() + sep + 1, comp.getBuffer().end()); - const string::size_type count = flagsString.length(); - - int flags = 0; - - for (string::size_type i = 0 ; i < count ; ++i) - { - switch (flagsString[i]) - { - case 'R': case 'r': flags |= message::FLAG_REPLIED; break; - case 'S': case 's': flags |= message::FLAG_SEEN; break; - case 'T': case 't': flags |= message::FLAG_DELETED; break; - case 'F': case 'f': flags |= message::FLAG_MARKED; break; - case 'P': case 'p': flags |= message::FLAG_PASSED; break; - } - } - - return (flags); -} - - -const utility::file::path::component maildirUtils::buildFlags(const int flags) -{ - string str; - str.reserve(8); - - str += "2,"; - - if (flags & message::FLAG_MARKED) str += "F"; - if (flags & message::FLAG_PASSED) str += "P"; - if (flags & message::FLAG_REPLIED) str += "R"; - if (flags & message::FLAG_SEEN) str += "S"; - if (flags & message::FLAG_DELETED) str += "T"; - - return (utility::file::path::component(str)); -} - - -const utility::file::path::component maildirUtils::buildFilename - (const utility::file::path::component& id, const int flags) -{ - return (buildFilename(id, buildFlags(flags))); -} - - -const utility::file::path::component maildirUtils::buildFilename - (const utility::file::path::component& id, const utility::file::path::component& flags) -{ - return (utility::path::component(id.getBuffer() + ":" + flags.getBuffer())); -} - - -const utility::file::path::component maildirUtils::generateId() -{ - std::ostringstream oss; - - oss << utility::random::getTime(); - oss << "."; - oss << utility::random::getProcess(); - oss << "."; - oss << utility::random::getString(6); - - return (utility::file::path::component(oss.str())); -} - - - -// -// messageIdComparator -// - -maildirUtils::messageIdComparator::messageIdComparator - (const utility::file::path::component& comp) - : m_comp(maildirUtils::extractId(comp)) -{ -} - - -const bool maildirUtils::messageIdComparator::operator() - (const utility::file::path::component& other) const -{ - return (m_comp == maildirUtils::extractId(other)); -} - - -} // messaging -} // vmime diff --git a/src/messaging/pop3/POP3Folder.cpp b/src/messaging/pop3/POP3Folder.cpp new file mode 100644 index 00000000..c70e2b5b --- /dev/null +++ b/src/messaging/pop3/POP3Folder.cpp @@ -0,0 +1,820 @@ +// +// 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/pop3/POP3Folder.hpp" + +#include "vmime/messaging/pop3/POP3Store.hpp" +#include "vmime/messaging/pop3/POP3Message.hpp" + +#include "vmime/exception.hpp" + + +namespace vmime { +namespace messaging { +namespace pop3 { + + +POP3Folder::POP3Folder(const folder::path& path, POP3Store* store) + : m_store(store), m_path(path), + m_name(path.isEmpty() ? folder::path::component("") : path.getLastComponent()), + m_mode(-1), m_open(false) +{ + m_store->registerFolder(this); +} + + +POP3Folder::~POP3Folder() +{ + if (m_store) + { + if (m_open) + close(false); + + m_store->unregisterFolder(this); + } + else if (m_open) + { + onClose(); + } +} + + +const int POP3Folder::getMode() const +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + return (m_mode); +} + + +const int POP3Folder::getType() +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + if (m_path.isEmpty()) + return (TYPE_CONTAINS_FOLDERS); + else if (m_path.getSize() == 1 && m_path[0].getBuffer() == "INBOX") + return (TYPE_CONTAINS_MESSAGES); + else + throw exceptions::folder_not_found(); +} + + +const int POP3Folder::getFlags() +{ + return (0); +} + + +const folder::path::component POP3Folder::getName() const +{ + return (m_name); +} + + +const folder::path POP3Folder::getFullPath() const +{ + return (m_path); +} + + +void POP3Folder::open(const int mode, bool failIfModeIsNotAvailable) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + + if (m_path.isEmpty()) + { + if (mode != MODE_READ_ONLY && failIfModeIsNotAvailable) + throw exceptions::operation_not_supported(); + + m_open = true; + m_mode = mode; + + m_messageCount = 0; + } + else if (m_path.getSize() == 1 && m_path[0].getBuffer() == "INBOX") + { + m_store->sendRequest("STAT"); + + string response; + m_store->readResponse(response, false); + + if (!m_store->isSuccessResponse(response)) + throw exceptions::command_error("STAT", response); + + m_store->stripResponseCode(response, response); + + std::istringstream iss(response); + iss >> m_messageCount; + + if (iss.fail()) + throw exceptions::invalid_response("STAT", response); + + m_open = true; + m_mode = mode; + } + else + { + throw exceptions::folder_not_found(); + } +} + +void POP3Folder::close(const bool expunge) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + if (!expunge) + { + m_store->sendRequest("RSET"); + + string response; + m_store->readResponse(response, false); + } + + m_open = false; + m_mode = -1; + + onClose(); +} + + +void POP3Folder::onClose() +{ + for (MessageMap::iterator it = m_messages.begin() ; it != m_messages.end() ; ++it) + (*it).first->onFolderClosed(); + + m_messages.clear(); +} + + +void POP3Folder::create(const int /* type */) +{ + throw exceptions::operation_not_supported(); +} + + +const bool POP3Folder::exists() +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + + return (m_path.isEmpty() || (m_path.getSize() == 1 && m_path[0].getBuffer() == "INBOX")); +} + + +const bool POP3Folder::isOpen() const +{ + return (m_open); +} + + +message* POP3Folder::getMessage(const int num) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + else if (num < 1 || num > m_messageCount) + throw exceptions::message_not_found(); + + return new POP3Message(this, num); +} + + +std::vector POP3Folder::getMessages(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 (to < from || from < 1 || to < 1 || from > m_messageCount || to > m_messageCount) + throw exceptions::message_not_found(); + + std::vector v; + + for (int i = from ; i <= to ; ++i) + v.push_back(new POP3Message(this, i)); + + return (v); +} + + +std::vector POP3Folder::getMessages(const std::vector & nums) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + std::vector v; + + try + { + for (std::vector ::const_iterator it = nums.begin() ; it != nums.end() ; ++it) + { + if (*it < 1|| *it > m_messageCount) + throw exceptions::message_not_found(); + + v.push_back(new POP3Message(this, *it)); + } + } + catch (std::exception& e) + { + for (std::vector ::iterator it = v.begin() ; it != v.end() ; ++it) + delete (*it); + + throw; + } + + return (v); +} + + +const int POP3Folder::getMessageCount() +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + return (m_messageCount); +} + + +folder* POP3Folder::getFolder(const folder::path::component& name) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + + return new POP3Folder(m_path / name, m_store); +} + + +std::vector POP3Folder::getFolders(const bool /* recursive */) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + + if (m_path.isEmpty()) + { + std::vector v; + v.push_back(new POP3Folder(folder::path::component("INBOX"), m_store)); + return (v); + } + else + { + std::vector v; + return (v); + } +} + + +void POP3Folder::fetchMessages(std::vector & 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 ::iterator it = msg.begin() ; + it != msg.end() ; ++it) + { + dynamic_cast (*it)->fetch(this, options); + + if (progress) + progress->progress(++current, total); + } + + if (options & FETCH_SIZE) + { + // Send the "LIST" command + std::ostringstream command; + command << "LIST"; + + m_store->sendRequest(command.str()); + + // Get the response + string response; + m_store->readResponse(response, true, NULL); + + if (m_store->isSuccessResponse(response)) + { + m_store->stripFirstLine(response, response, NULL); + + // C: LIST + // S: +OK + // S: 1 47548 + // S: 2 12653 + // S: . + std::map result; + parseMultiListOrUidlResponse(response, result); + + for (std::vector ::iterator it = msg.begin() ; + it != msg.end() ; ++it) + { + POP3Message* m = dynamic_cast (*it); + + std::map ::const_iterator x = result.find(m->m_num); + + if (x != result.end()) + { + int size = 0; + + std::istringstream iss((*x).second); + iss >> size; + + m->m_size = size; + } + } + } + + } + + if (options & FETCH_UID) + { + // Send the "UIDL" command + std::ostringstream command; + command << "UIDL"; + + m_store->sendRequest(command.str()); + + // Get the response + string response; + m_store->readResponse(response, true, NULL); + + if (m_store->isSuccessResponse(response)) + { + m_store->stripFirstLine(response, response, NULL); + + // C: UIDL + // S: +OK + // S: 1 whqtswO00WBw418f9t5JxYwZ + // S: 2 QhdPYR:00WBw1Ph7x7 + // S: . + std::map result; + parseMultiListOrUidlResponse(response, result); + + for (std::vector ::iterator it = msg.begin() ; + it != msg.end() ; ++it) + { + POP3Message* m = dynamic_cast (*it); + + std::map ::const_iterator x = result.find(m->m_num); + + if (x != result.end()) + m->m_uid = (*x).second; + } + } + } + + if (progress) + progress->stop(total); +} + + +void POP3Folder::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 (msg)->fetch(this, options); + + if (options & FETCH_SIZE) + { + // Send the "LIST" command + std::ostringstream command; + command << "LIST " << msg->getNumber(); + + m_store->sendRequest(command.str()); + + // Get the response + string response; + m_store->readResponse(response, false, NULL); + + if (m_store->isSuccessResponse(response)) + { + m_store->stripResponseCode(response, response); + + // C: LIST 2 + // S: +OK 2 4242 + string::iterator it = response.begin(); + + while (it != response.end() && (*it == ' ' || *it == '\t')) ++it; + while (it != response.end() && !(*it == ' ' || *it == '\t')) ++it; + while (it != response.end() && (*it == ' ' || *it == '\t')) ++it; + + if (it != response.end()) + { + int size = 0; + + std::istringstream iss(string(it, response.end())); + iss >> size; + + dynamic_cast (msg)->m_size = size; + } + } + } + + if (options & FETCH_UID) + { + // Send the "UIDL" command + std::ostringstream command; + command << "UIDL " << msg->getNumber(); + + m_store->sendRequest(command.str()); + + // Get the response + string response; + m_store->readResponse(response, false, NULL); + + if (m_store->isSuccessResponse(response)) + { + m_store->stripResponseCode(response, response); + + // C: UIDL 2 + // S: +OK 2 QhdPYR:00WBw1Ph7x7 + string::iterator it = response.begin(); + + while (it != response.end() && (*it == ' ' || *it == '\t')) ++it; + while (it != response.end() && !(*it == ' ' || *it == '\t')) ++it; + while (it != response.end() && (*it == ' ' || *it == '\t')) ++it; + + if (it != response.end()) + { + dynamic_cast (msg)->m_uid = + string(it, response.end()); + } + } + } +} + + +const int POP3Folder::getFetchCapabilities() const +{ + return (FETCH_ENVELOPE | FETCH_CONTENT_INFO | + FETCH_SIZE | FETCH_FULL_HEADER | FETCH_UID); +} + + +folder* POP3Folder::getParent() +{ + return (m_path.isEmpty() ? NULL : new POP3Folder(m_path.getParent(), m_store)); +} + + +const store* POP3Folder::getStore() const +{ + return (m_store); +} + + +store* POP3Folder::getStore() +{ + return (m_store); +} + + +void POP3Folder::registerMessage(POP3Message* msg) +{ + m_messages.insert(MessageMap::value_type(msg, msg->getNumber())); +} + + +void POP3Folder::unregisterMessage(POP3Message* msg) +{ + m_messages.erase(msg); +} + + +void POP3Folder::onStoreDisconnected() +{ + m_store = NULL; +} + + +void POP3Folder::deleteMessage(const int num) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + std::ostringstream command; + command << "DELE " << num; + + m_store->sendRequest(command.str()); + + string response; + m_store->readResponse(response, false); + + if (!m_store->isSuccessResponse(response)) + throw exceptions::command_error("DELE", response); + + // Update local flags + for (std::map ::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + POP3Message* msg = (*it).first; + + if (msg->getNumber() == num) + msg->m_deleted = true; + } + + // Notify message flags changed + std::vector nums; + nums.push_back(num); + + events::messageChangedEvent event(this, events::messageChangedEvent::TYPE_FLAGS, nums); + + notifyMessageChanged(event); +} + + +void POP3Folder::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"); + + const int to2 = (to == -1 ? m_messageCount : to); + + for (int i = from ; i <= to2 ; ++i) + { + std::ostringstream command; + command << "DELE " << i; + + m_store->sendRequest(command.str()); + + string response; + m_store->readResponse(response, false); + + if (!m_store->isSuccessResponse(response)) + throw exceptions::command_error("DELE", response); + } + + // Update local flags + for (std::map ::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + POP3Message* msg = (*it).first; + + if (msg->getNumber() >= from && msg->getNumber() <= to2) + msg->m_deleted = true; + } + + // Notify message flags changed + std::vector nums; + + for (int i = from ; i <= to2 ; ++i) + nums.push_back(i); + + events::messageChangedEvent event(this, events::messageChangedEvent::TYPE_FLAGS, nums); + + notifyMessageChanged(event); +} + + +void POP3Folder::deleteMessages(const std::vector & 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"); + + for (std::vector ::const_iterator + it = nums.begin() ; it != nums.end() ; ++it) + { + std::ostringstream command; + command << "DELE " << (*it); + + m_store->sendRequest(command.str()); + + string response; + m_store->readResponse(response, false); + + if (!m_store->isSuccessResponse(response)) + throw exceptions::command_error("DELE", response); + } + + // Sort message list + std::vector list; + + list.resize(nums.size()); + std::copy(nums.begin(), nums.end(), list.begin()); + + std::sort(list.begin(), list.end()); + + // Update local flags + for (std::map ::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + POP3Message* msg = (*it).first; + + if (std::binary_search(list.begin(), list.end(), msg->getNumber())) + msg->m_deleted = true; + } + + // Notify message flags changed + events::messageChangedEvent event(this, events::messageChangedEvent::TYPE_FLAGS, list); + + notifyMessageChanged(event); +} + + +void POP3Folder::setMessageFlags(const int /* from */, const int /* to */, + const int /* flags */, const int /* mode */) +{ + throw exceptions::operation_not_supported(); +} + + +void POP3Folder::setMessageFlags(const std::vector & /* nums */, + const int /* flags */, const int /* mode */) +{ + throw exceptions::operation_not_supported(); +} + + +void POP3Folder::rename(const folder::path& /* newPath */) +{ + throw exceptions::operation_not_supported(); +} + + +void POP3Folder::addMessage(vmime::message* /* msg */, const int /* flags */, + vmime::datetime* /* date */, progressionListener* /* progress */) +{ + throw exceptions::operation_not_supported(); +} + + +void POP3Folder::addMessage(utility::inputStream& /* is */, const int /* size */, const int /* flags */, + vmime::datetime* /* date */, progressionListener* /* progress */) +{ + throw exceptions::operation_not_supported(); +} + + +void POP3Folder::copyMessage(const folder::path& /* dest */, const int /* num */) +{ + throw exceptions::operation_not_supported(); +} + + +void POP3Folder::copyMessages(const folder::path& /* dest */, const int /* from */, const int /* to */) +{ + throw exceptions::operation_not_supported(); +} + + +void POP3Folder::copyMessages(const folder::path& /* dest */, const std::vector & /* nums */) +{ + throw exceptions::operation_not_supported(); +} + + +void POP3Folder::status(int& count, int& unseen) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + m_store->sendRequest("STAT"); + + string response; + m_store->readResponse(response, false); + + if (!m_store->isSuccessResponse(response)) + throw exceptions::command_error("STAT", response); + + m_store->stripResponseCode(response, response); + + std::istringstream iss(response); + iss >> count; + + unseen = count; + + // Update local message count + if (m_messageCount != count) + { + const int oldCount = m_messageCount; + + m_messageCount = count; + + if (count > oldCount) + { + std::vector nums; + nums.reserve(count - oldCount); + + for (int i = oldCount + 1, j = 0 ; i <= count ; ++i, ++j) + nums[j] = i; + + // Notify message count changed + events::messageCountEvent event(this, events::messageCountEvent::TYPE_ADDED, nums); + + notifyMessageCount(event); + + // Notify folders with the same path + for (std::list ::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); + } + } + } + } +} + + +void POP3Folder::expunge() +{ + // Not supported by POP3 protocol (deleted messages are automatically + // expunged at the end of the session...). +} + + +void POP3Folder::parseMultiListOrUidlResponse(const string& response, std::map & result) +{ + std::istringstream iss(response); + std::map ids; + + string line; + + while (std::getline(iss, line)) + { + string::iterator it = line.begin(); + + while (it != line.end() && (*it == ' ' || *it == '\t')) + ++it; + + if (it != line.end()) + { + int number = 0; + + while (it != line.end() && (*it >= '0' && *it <= '9')) + { + number = (number * 10) + (*it - '0'); + ++it; + } + + while (it != line.end() && !(*it == ' ' || *it == '\t')) ++it; + while (it != line.end() && (*it == ' ' || *it == '\t')) ++it; + + if (it != line.end()) + { + result.insert(std::map ::value_type(number, string(it, line.end()))); + } + } + } +} + + +} // pop3 +} // messaging +} // vmime diff --git a/src/messaging/pop3/POP3Message.cpp b/src/messaging/pop3/POP3Message.cpp new file mode 100644 index 00000000..01680d34 --- /dev/null +++ b/src/messaging/pop3/POP3Message.cpp @@ -0,0 +1,218 @@ +// +// 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/pop3/POP3Message.hpp" +#include "vmime/messaging/pop3/POP3Folder.hpp" +#include "vmime/messaging/pop3/POP3Store.hpp" + +#include + + +namespace vmime { +namespace messaging { +namespace pop3 { + + +POP3Message::POP3Message(POP3Folder* folder, const int num) + : m_folder(folder), m_num(num), m_size(-1), m_deleted(false), m_header(NULL) +{ + m_folder->registerMessage(this); +} + + +POP3Message::~POP3Message() +{ + if (m_folder) + m_folder->unregisterMessage(this); + + delete (m_header); +} + + +void POP3Message::onFolderClosed() +{ + m_folder = NULL; +} + + +const int POP3Message::getNumber() const +{ + return (m_num); +} + + +const message::uid POP3Message::getUniqueId() const +{ + return (m_uid); +} + + +const int POP3Message::getSize() const +{ + if (m_size == -1) + throw exceptions::unfetched_object(); + + return (m_size); +} + + +const bool POP3Message::isExpunged() const +{ + return (false); +} + + +const int POP3Message::getFlags() const +{ + int flags = FLAG_RECENT; + + if (m_deleted) + flags |= FLAG_DELETED; + + return (flags); +} + + +const structure& POP3Message::getStructure() const +{ + throw exceptions::operation_not_supported(); +} + + +structure& POP3Message::getStructure() +{ + throw exceptions::operation_not_supported(); +} + + +const header& POP3Message::getHeader() const +{ + if (m_header == NULL) + throw exceptions::unfetched_object(); + + return (*m_header); +} + + +void POP3Message::extract(utility::outputStream& os, progressionListener* progress, + const int start, const int length) const +{ + if (!m_folder) + throw exceptions::illegal_state("Folder closed"); + else if (!m_folder->m_store) + throw exceptions::illegal_state("Store disconnected"); + + if (start != 0 && length != -1) + throw exceptions::partial_fetch_not_supported(); + + // Emit the "RETR" command + std::ostringstream oss; + oss << "RETR " << m_num; + + const_cast (m_folder)->m_store->sendRequest(oss.str()); + + try + { + POP3Folder::MessageMap::const_iterator it = + m_folder->m_messages.find(const_cast (this)); + + const int totalSize = (it != m_folder->m_messages.end()) + ? (*it).second : 0; + + const_cast (m_folder)->m_store-> + readResponse(os, progress, totalSize); + } + catch (exceptions::command_error& e) + { + throw exceptions::command_error("RETR", e.response()); + } +} + + +void POP3Message::extractPart + (const part& /* p */, utility::outputStream& /* os */, progressionListener* /* progress */, + const int /* start */, const int /* length */) const +{ + throw exceptions::operation_not_supported(); +} + + +void POP3Message::fetchPartHeader(part& /* p */) +{ + throw exceptions::operation_not_supported(); +} + + +void POP3Message::fetch(POP3Folder* folder, const int options) +{ + if (m_folder != folder) + throw exceptions::folder_not_found(); + + // FETCH_STRUCTURE and FETCH_FLAGS are not supported by POP3. + if (options & (folder::FETCH_STRUCTURE | folder::FETCH_FLAGS)) + throw exceptions::operation_not_supported(); + + // Check for the real need to fetch the full header + if (!((options & folder::FETCH_ENVELOPE) || + (options & folder::FETCH_CONTENT_INFO) || + (options & folder::FETCH_FULL_HEADER))) + { + return; + } + + // No need to differenciate between FETCH_ENVELOPE, + // FETCH_CONTENT_INFO, ... since POP3 only permits to + // retrieve the whole header and not fields in particular. + + // Emit the "TOP" command + std::ostringstream oss; + oss << "TOP " << m_num << " 0"; + + m_folder->m_store->sendRequest(oss.str()); + + try + { + string buffer; + m_folder->m_store->readResponse(buffer, true); + + if (m_header != NULL) + { + delete (m_header); + m_header = NULL; + } + + m_header = new header(); + m_header->parse(buffer); + } + catch (exceptions::command_error& e) + { + throw exceptions::command_error("TOP", e.response()); + } +} + + +void POP3Message::setFlags(const int /* flags */, const int /* mode */) +{ + throw exceptions::operation_not_supported(); +} + + +} // pop3 +} // messaging +} // vmime diff --git a/src/messaging/pop3/POP3Store.cpp b/src/messaging/pop3/POP3Store.cpp new file mode 100644 index 00000000..d6ece2a0 --- /dev/null +++ b/src/messaging/pop3/POP3Store.cpp @@ -0,0 +1,630 @@ +// +// 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/pop3/POP3Store.hpp" +#include "vmime/messaging/pop3/POP3Folder.hpp" + +#include "vmime/exception.hpp" +#include "vmime/platformDependant.hpp" +#include "vmime/messageId.hpp" +#include "vmime/utility/md5.hpp" + +#include + + +namespace vmime { +namespace messaging { +namespace pop3 { + + +POP3Store::POP3Store(session* sess, authenticator* auth) + : store(sess, getInfosInstance(), auth), m_socket(NULL), + m_authentified(false), m_timeoutHandler(NULL) +{ +} + + +POP3Store::~POP3Store() +{ + if (isConnected()) + disconnect(); + else if (m_socket) + internalDisconnect(); +} + + +const string POP3Store::getProtocolName() const +{ + return "pop3"; +} + + +folder* POP3Store::getDefaultFolder() +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return new POP3Folder(folder::path(folder::path::component("INBOX")), this); +} + + +folder* POP3Store::getRootFolder() +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return new POP3Folder(folder::path(), this); +} + + +folder* POP3Store::getFolder(const folder::path& path) +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return new POP3Folder(path, this); +} + + +const bool POP3Store::isValidFolderName(const folder::path::component& /* name */) const +{ + return true; +} + + +void POP3Store::connect() +{ + if (isConnected()) + throw exceptions::already_connected(); + + const string address = getSession()->getProperties()[sm_infos.getPropertyPrefix() + "server.address"]; + const port_t port = getSession()->getProperties().getProperty(sm_infos.getPropertyPrefix() + "server.port", sm_infos.getDefaultPort()); + + // Create the time-out handler + if (getSession()->getProperties().hasProperty + (sm_infos.getPropertyPrefix() + "timeout.factory")) + { + timeoutHandlerFactory* tof = platformDependant::getHandler()-> + getTimeoutHandlerFactory(getSession()->getProperties() + [sm_infos.getPropertyPrefix() + "timeout.factory"]); + + m_timeoutHandler = tof->create(); + } + + // Create and connect the socket + socketFactory* sf = platformDependant::getHandler()->getSocketFactory + (getSession()->getProperties().getProperty(sm_infos.getPropertyPrefix() + "server.socket-factory", string("default"))); + + m_socket = sf->create(); + m_socket->connect(address, port); + + // Connection + // + // eg: C: + // --- S: +OK MailSite POP3 Server 5.3.4.0 Ready <36938848.1056800841.634@somewhere.com> + + string response; + readResponse(response, false); + + if (isSuccessResponse(response)) + { + bool authentified = false; + + const authenticationInfos auth = getAuthenticator()->requestAuthInfos(); + + // Secured authentication with APOP (if requested and if available) + // + // eg: C: APOP vincent + // --- S: +OK vincent is a valid mailbox + messageId mid(response); + + if (getSession()->getProperties().getProperty(sm_infos.getPropertyPrefix() + "options.apop", false)) + { + if (mid.getLeft().length() && mid.getRight().length()) + { + // is the result of MD5 applied to "password" + sendRequest("APOP " + auth.getUsername() + " " + + utility::md5(mid.generate() + auth.getPassword()).hex()); + readResponse(response, false); + + if (isSuccessResponse(response)) + { + authentified = true; + } + else + { + if (getSession()->getProperties().getProperty(sm_infos.getPropertyPrefix() + + "options.apop.fallback", false) == false) + { + internalDisconnect(); + throw exceptions::authentication_error(response); + } + } + } + else + { + // APOP not supported + if (getSession()->getProperties().getProperty(sm_infos.getPropertyPrefix() + + "options.apop.fallback", false) == false) + { + // Can't fallback on basic authentification + internalDisconnect(); + throw exceptions::unsupported_option(); + } + } + } + + if (!authentified) + { + // Basic authentication + // + // eg: C: USER vincent + // --- S: +OK vincent is a valid mailbox + // + // C: PASS couic + // S: +OK vincent's maildrop has 2 messages (320 octets) + + sendRequest("USER " + auth.getUsername()); + readResponse(response, false); + + if (isSuccessResponse(response)) + { + sendRequest("PASS " + auth.getPassword()); + readResponse(response, false); + + if (!isSuccessResponse(response)) + { + internalDisconnect(); + throw exceptions::authentication_error(response); + } + } + else + { + internalDisconnect(); + throw exceptions::authentication_error(response); + } + } + } + else + { + internalDisconnect(); + throw exceptions::connection_greeting_error(response); + } + + m_authentified = true; +} + + +const bool POP3Store::isConnected() const +{ + return (m_socket && m_socket->isConnected() && m_authentified); +} + + +void POP3Store::disconnect() +{ + if (!isConnected()) + throw exceptions::not_connected(); + + internalDisconnect(); +} + + +void POP3Store::internalDisconnect() +{ + for (std::list ::iterator it = m_folders.begin() ; + it != m_folders.end() ; ++it) + { + (*it)->onStoreDisconnected(); + } + + m_folders.clear(); + + + sendRequest("QUIT"); + + m_socket->disconnect(); + + delete (m_socket); + m_socket = NULL; + + delete (m_timeoutHandler); + m_timeoutHandler = NULL; + + m_authentified = false; +} + + +void POP3Store::noop() +{ + m_socket->send("NOOP"); + + string response; + readResponse(response, false); + + if (!isSuccessResponse(response)) + throw exceptions::command_error("NOOP", response); +} + + +const bool POP3Store::isSuccessResponse(const string& buffer) +{ + static const string OK("+OK"); + + return (buffer.length() >= 3 && + std::equal(buffer.begin(), buffer.begin() + 3, OK.begin())); +} + + +const bool POP3Store::stripFirstLine(const string& buffer, string& result, string* firstLine) +{ + const string::size_type end = buffer.find('\n'); + + if (end != string::npos) + { + if (firstLine) *firstLine = buffer.substr(0, end); + result = buffer.substr(end + 1); + return (true); + } + else + { + result = buffer; + return (false); + } +} + + +void POP3Store::stripResponseCode(const string& buffer, string& result) +{ + const string::size_type pos = buffer.find_first_of(" \t"); + + if (pos != string::npos) + result = buffer.substr(pos + 1); + else + result = buffer; +} + + +void POP3Store::sendRequest(const string& buffer, const bool end) +{ + if (end) + m_socket->send(buffer + "\r\n"); + else + m_socket->send(buffer); +} + + +void POP3Store::readResponse(string& buffer, const bool multiLine, + progressionListener* progress) +{ + bool foundTerminator = false; + int current = 0, total = 0; + + if (progress) + progress->start(total); + + if (m_timeoutHandler) + m_timeoutHandler->resetTimeOut(); + + buffer.clear(); + + string::value_type last1 = '\0', last2 = '\0'; + + for ( ; !foundTerminator ; ) + { +#if 0 // not supported + // Check for possible cancellation + if (progress && progress->cancel()) + throw exceptions::operation_cancelled(); +#endif + + // Check whether the time-out delay is elapsed + if (m_timeoutHandler && m_timeoutHandler->isTimeOut()) + { + if (!m_timeoutHandler->handleTimeOut()) + throw exceptions::operation_timed_out(); + } + + // Receive data from the socket + string receiveBuffer; + m_socket->receive(receiveBuffer); + + if (receiveBuffer.empty()) // buffer is empty + { + platformDependant::getHandler()->wait(); + continue; + } + + // We have received data: reset the time-out counter + if (m_timeoutHandler) + m_timeoutHandler->resetTimeOut(); + + // Check for transparent characters: '\n..' becomes '\n.' + const string::value_type first = receiveBuffer[0]; + + if (first == '.' && last2 == '\n' && last1 == '.') + { + receiveBuffer.erase(receiveBuffer.begin()); + } + else if (receiveBuffer.length() >= 2 && first == '.' && + receiveBuffer[1] == '.' && last1 == '\n') + { + receiveBuffer.erase(receiveBuffer.begin()); + } + + for (string::size_type trans ; + string::npos != (trans = receiveBuffer.find("\n..")) ; ) + { + receiveBuffer.replace(trans, 3, "\n."); + } + + last1 = receiveBuffer[receiveBuffer.length() - 1]; + last2 = (receiveBuffer.length() >= 2) ? receiveBuffer[receiveBuffer.length() - 2] : 0; + + // Append the data to the response buffer + buffer += receiveBuffer; + current += receiveBuffer.length(); + + // Check for terminator string (and strip it if present) + foundTerminator = checkTerminator(buffer, multiLine); + + // Notify progression + if (progress) + { + total = std::max(total, current); + progress->progress(current, total); + } + + // If there is an error (-ERR) when executing a command that + // requires a multi-line response, the error response will + // include only one line, so we stop waiting for a multi-line + // terminator and check for a "normal" one. + if (multiLine && !foundTerminator && buffer.length() >= 4 && buffer[0] == '-') + { + foundTerminator = checkTerminator(buffer, false); + } + } + + if (progress) + progress->stop(total); +} + + +void POP3Store::readResponse(utility::outputStream& os, progressionListener* progress, + const int predictedSize) +{ + bool foundTerminator = false; + int current = 0, total = predictedSize; + + string temp; + bool codeDone = false; + + if (progress) + progress->start(total); + + if (m_timeoutHandler) + m_timeoutHandler->resetTimeOut(); + + string::value_type last1 = '\0', last2 = '\0'; + + for ( ; !foundTerminator ; ) + { +#if 0 // not supported + // Check for possible cancellation + if (progress && progress->cancel()) + throw exceptions::operation_cancelled(); +#endif + + // Check whether the time-out delay is elapsed + if (m_timeoutHandler && m_timeoutHandler->isTimeOut()) + { + if (!m_timeoutHandler->handleTimeOut()) + throw exceptions::operation_timed_out(); + } + + // Receive data from the socket + string receiveBuffer; + m_socket->receive(receiveBuffer); + + if (receiveBuffer.empty()) // buffer is empty + { + platformDependant::getHandler()->wait(); + continue; + } + + // We have received data: reset the time-out counter + if (m_timeoutHandler) + m_timeoutHandler->resetTimeOut(); + + // Check for transparent characters: '\n..' becomes '\n.' + const string::value_type first = receiveBuffer[0]; + + if (first == '.' && last2 == '\n' && last1 == '.') + { + receiveBuffer.erase(receiveBuffer.begin()); + } + else if (receiveBuffer.length() >= 2 && first == '.' && + receiveBuffer[1] == '.' && last1 == '\n') + { + receiveBuffer.erase(receiveBuffer.begin()); + } + + for (string::size_type trans ; + string::npos != (trans = receiveBuffer.find("\n..")) ; ) + { + receiveBuffer.replace(trans, 3, "\n."); + } + + last1 = receiveBuffer[receiveBuffer.length() - 1]; + last2 = (receiveBuffer.length() >= 2) ? receiveBuffer[receiveBuffer.length() - 2] : 0; + + // If we don't have extracted the response code yet + if (!codeDone) + { + temp += receiveBuffer; + + string firstLine; + + if (stripFirstLine(temp, temp, &firstLine) == true) + { + if (!isSuccessResponse(firstLine)) + throw exceptions::command_error("?", firstLine); + + receiveBuffer = temp; + temp.clear(); + + codeDone = true; + } + } + + if (codeDone) + { + // Check for terminator string (and strip it if present) + foundTerminator = checkTerminator(receiveBuffer, true); + + // Inject the data into the output stream + os.write(receiveBuffer.data(), receiveBuffer.length()); + current += receiveBuffer.length(); + + // Notify progression + if (progress) + { + total = std::max(total, current); + progress->progress(current, total); + } + } + } + + if (progress) + progress->stop(total); +} + + +const bool POP3Store::checkTerminator(string& buffer, const bool multiLine) +{ + // Multi-line response + if (multiLine) + { + static const string term1("\r\n.\r\n"); + static const string term2("\n.\n"); + + return (checkOneTerminator(buffer, term1) || + checkOneTerminator(buffer, term2)); + } + // Normal response + else + { + static const string term1("\r\n"); + static const string term2("\n"); + + return (checkOneTerminator(buffer, term1) || + checkOneTerminator(buffer, term2)); + } + + return (false); +} + + +const bool POP3Store::checkOneTerminator(string& buffer, const string& term) +{ + if (buffer.length() >= term.length() && + std::equal(buffer.end() - term.length(), buffer.end(), term.begin())) + { + buffer.erase(buffer.end() - term.length(), buffer.end()); + return (true); + } + + return (false); +} + + +void POP3Store::registerFolder(POP3Folder* folder) +{ + m_folders.push_back(folder); +} + + +void POP3Store::unregisterFolder(POP3Folder* folder) +{ + std::list ::iterator it = std::find(m_folders.begin(), m_folders.end(), folder); + if (it != m_folders.end()) m_folders.erase(it); +} + + +const int POP3Store::getCapabilities() const +{ + return (CAPABILITY_DELETE_MESSAGE); +} + + + +// Service infos + +POP3Store::_infos POP3Store::sm_infos; + + +const serviceInfos& POP3Store::getInfosInstance() +{ + return (sm_infos); +} + + +const serviceInfos& POP3Store::getInfos() const +{ + return (sm_infos); +} + + +const port_t POP3Store::_infos::getDefaultPort() const +{ + return (110); +} + + +const string POP3Store::_infos::getPropertyPrefix() const +{ + return "store.pop3."; +} + + +const std::vector POP3Store::_infos::getAvailableProperties() const +{ + std::vector list; + + // POP3-specific options + list.push_back("options.apop"); + list.push_back("options.apop.fallback"); + + // Common properties + list.push_back("auth.username"); + list.push_back("auth.password"); + + list.push_back("server.address"); + list.push_back("server.port"); + list.push_back("server.socket-factory"); + + list.push_back("timeout.factory"); + + return (list); +} + + +} // pop3 +} // messaging +} // vmime diff --git a/src/messaging/smtp/SMTPTransport.cpp b/src/messaging/smtp/SMTPTransport.cpp new file mode 100644 index 00000000..6e7ab240 --- /dev/null +++ b/src/messaging/smtp/SMTPTransport.cpp @@ -0,0 +1,593 @@ +// +// 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/smtp/SMTPTransport.hpp" + +#include "vmime/exception.hpp" +#include "vmime/platformDependant.hpp" +#include "vmime/encoderB64.hpp" +#include "vmime/message.hpp" +#include "vmime/mailboxList.hpp" + +#include "vmime/messaging/authHelper.hpp" + + +namespace vmime { +namespace messaging { +namespace smtp { + + +SMTPTransport::SMTPTransport(session* sess, authenticator* auth) + : transport(sess, getInfosInstance(), auth), m_socket(NULL), + m_authentified(false), m_extendedSMTP(false), m_timeoutHandler(NULL) +{ +} + + +SMTPTransport::~SMTPTransport() +{ + if (isConnected()) + disconnect(); + else if (m_socket) + internalDisconnect(); +} + + +const string SMTPTransport::getProtocolName() const +{ + return "smtp"; +} + + +void SMTPTransport::connect() +{ + if (isConnected()) + throw exceptions::already_connected(); + + const string address = getSession()->getProperties()[sm_infos.getPropertyPrefix() + "server.address"]; + const port_t port = getSession()->getProperties().getProperty(sm_infos.getPropertyPrefix() + "server.port", sm_infos.getDefaultPort()); + + // Create the time-out handler + if (getSession()->getProperties().hasProperty + (sm_infos.getPropertyPrefix() + "timeout.factory")) + { + timeoutHandlerFactory* tof = platformDependant::getHandler()-> + getTimeoutHandlerFactory(getSession()->getProperties() + [sm_infos.getPropertyPrefix() + "timeout.factory"]); + + m_timeoutHandler = tof->create(); + } + + // Create and connect the socket + socketFactory* sf = platformDependant::getHandler()->getSocketFactory + (getSession()->getProperties().getProperty(sm_infos.getPropertyPrefix() + "server.socket-factory", string("default"))); + + m_socket = sf->create(); + m_socket->connect(address, port); + + // Connection + // + // eg: C: + // --- S: 220 smtp.domain.com Service ready + + string response; + readResponse(response); + + if (responseCode(response) != 220) + { + internalDisconnect(); + throw exceptions::connection_greeting_error(response); + } + + // Identification + // First, try Extended SMTP (ESMTP) + // + // eg: C: EHLO thismachine.ourdomain.com + // S: 250 OK + + sendRequest("EHLO " + platformDependant::getHandler()->getHostName()); + readResponse(response); + + if (responseCode(response) != 250) + { + // Next, try "Basic" SMTP + // + // eg: C: HELO thismachine.ourdomain.com + // S: 250 OK + + sendRequest("HELO " + platformDependant::getHandler()->getHostName()); + readResponse(response); + + if (responseCode(response) != 250) + { + internalDisconnect(); + throw exceptions::connection_greeting_error(response); + } + + m_extendedSMTP = false; + } + else + { + m_extendedSMTP = true; + } + + // Authentication + if (getSession()->getProperties().getProperty + (sm_infos.getPropertyPrefix() + "options.need-authentication", false) == true) + { + if (!m_extendedSMTP) + { + internalDisconnect(); + throw exceptions::command_error("AUTH", "ESMTP not supported."); + } + + const authenticationInfos auth = getAuthenticator()->requestAuthInfos(); + bool authentified = false; + + enum AuthMethods + { + First = 0, + CRAM_MD5 = First, + // TODO: more authentication methods... + End + }; + + for (int currentMethod = First ; !authentified ; ++currentMethod) + { + switch (currentMethod) + { + case CRAM_MD5: + { + sendRequest("AUTH CRAM-MD5"); + readResponse(response); + + if (responseCode(response) == 334) + { + encoderB64 base64; + + string challengeB64 = responseText(response); + string challenge, challengeHex; + + { + utility::inputStreamStringAdapter in(challengeB64); + utility::outputStreamStringAdapter out(challenge); + + base64.decode(in, out); + } + + hmac_md5(challenge, auth.getPassword(), challengeHex); + + string decoded = auth.getUsername() + " " + challengeHex; + string encoded; + + { + utility::inputStreamStringAdapter in(decoded); + utility::outputStreamStringAdapter out(encoded); + + base64.encode(in, out); + } + + sendRequest(encoded); + readResponse(response); + + if (responseCode(response) == 235) + { + authentified = true; + } + else + { + internalDisconnect(); + throw exceptions::authentication_error(response); + } + } + + break; + } + case End: + { + // All authentication methods have been tried and + // the server does not understand any. + throw exceptions::authentication_error(response); + } + + } + } + } + + m_authentified = true; +} + + +const bool SMTPTransport::isConnected() const +{ + return (m_socket && m_socket->isConnected() && m_authentified); +} + + +void SMTPTransport::disconnect() +{ + if (!isConnected()) + throw exceptions::not_connected(); + + internalDisconnect(); +} + + +void SMTPTransport::internalDisconnect() +{ + sendRequest("QUIT"); + + m_socket->disconnect(); + + delete (m_socket); + m_socket = NULL; + + delete (m_timeoutHandler); + m_timeoutHandler = NULL; + + m_authentified = false; + m_extendedSMTP = false; +} + + +void SMTPTransport::noop() +{ + m_socket->send("NOOP"); + + string response; + readResponse(response); + + if (responseCode(response) != 250) + throw exceptions::command_error("NOOP", response); +} + + +static void extractMailboxes + (mailboxList& recipients, const addressList& list) +{ + for (int i = 0 ; i < list.getAddressCount() ; ++i) + { + mailbox* mbox = dynamic_cast (list.getAddressAt(i)->clone()); + + if (mbox != NULL) + recipients.appendMailbox(mbox); + } +} + + +void SMTPTransport::send(vmime::message* msg, progressionListener* progress) +{ + // Extract expeditor + mailbox expeditor; + + try + { + const mailboxField& from = dynamic_cast + (*msg->getHeader()->findField(fields::FROM)); + expeditor = from.getValue(); + } + catch (exceptions::no_such_field&) + { + throw exceptions::no_expeditor(); + } + + // Extract recipients + mailboxList recipients; + + try + { + const addressListField& to = dynamic_cast + (*msg->getHeader()->findField(fields::TO)); + extractMailboxes(recipients, to.getValue()); + } + catch (exceptions::no_such_field&) { } + + try + { + const addressListField& cc = dynamic_cast + (*msg->getHeader()->findField(fields::CC)); + extractMailboxes(recipients, cc.getValue()); + } + catch (exceptions::no_such_field&) { } + + try + { + const addressListField& bcc = dynamic_cast + (*msg->getHeader()->findField(fields::BCC)); + extractMailboxes(recipients, bcc.getValue()); + } + catch (exceptions::no_such_field&) { } + + // Generate the message, "stream" it and delegate the sending + // to the generic send() function. + std::ostringstream oss; + utility::outputStreamAdapter ossAdapter(oss); + + msg->generate(ossAdapter); + + const string& str(oss.str()); + + utility::inputStreamStringAdapter isAdapter(str); + + send(expeditor, recipients, isAdapter, str.length(), progress); +} + + +void SMTPTransport::send(const mailbox& expeditor, const mailboxList& recipients, + utility::inputStream& is, const utility::stream::size_type size, + progressionListener* progress) +{ + // If no recipient/expeditor was found, throw an exception + if (recipients.isEmpty()) + throw exceptions::no_recipient(); + else if (expeditor.isEmpty()) + throw exceptions::no_expeditor(); + + // Emit the "MAIL" command + string response; + + sendRequest("MAIL FROM: <" + expeditor.getEmail() + ">"); + readResponse(response); + + if (responseCode(response) != 250) + { + internalDisconnect(); + throw exceptions::command_error("MAIL", response); + } + + // Emit a "RCPT TO" command for each recipient + for (int i = 0 ; i < recipients.getMailboxCount() ; ++i) + { + const mailbox& mbox = *recipients.getMailboxAt(i); + + sendRequest("RCPT TO: <" + mbox.getEmail() + ">"); + readResponse(response); + + if (responseCode(response) != 250) + { + internalDisconnect(); + throw exceptions::command_error("RCPT TO", response); + } + } + + // Send the message data + sendRequest("DATA"); + readResponse(response); + + if (responseCode(response) != 354) + { + internalDisconnect(); + throw exceptions::command_error("DATA", response); + } + + int current = 0, total = size; + + if (progress) + progress->start(total); + + char buffer[65536]; + + while (!is.eof()) + { + const int read = is.read(buffer, sizeof(buffer)); + + // Transform '.' into '..' at the beginning of a line + char* start = buffer; + char* end = buffer + read; + char* pos = buffer; + + while ((pos = std::find(pos, end, '.')) != end) + { + if (pos > buffer && *(pos - 1) == '\n') + { + m_socket->sendRaw(start, pos - start); + m_socket->sendRaw(".", 1); + + start = pos; + } + + ++pos; + } + + // Send the remaining data + m_socket->sendRaw(start, end - start); + + current += read; + + // Notify progression + if (progress) + { + total = std::max(total, current); + progress->progress(current, total); + } + } + + if (progress) + progress->stop(total); + + m_socket->sendRaw("\r\n.\r\n", 5); + readResponse(response); + + if (responseCode(response) != 250) + { + internalDisconnect(); + throw exceptions::command_error("DATA", response); + } +} + + +void SMTPTransport::sendRequest(const string& buffer, const bool end) +{ + m_socket->send(buffer); + if (end) m_socket->send("\r\n"); +} + + +const int SMTPTransport::responseCode(const string& response) +{ + int code = 0; + + if (response.length() >= 3) + { + code = (response[0] - '0') * 100 + + (response[1] - '0') * 10 + + (response[2] - '0'); + } + + return (code); +} + + +const string SMTPTransport::responseText(const string& response) +{ + string text; + + std::istringstream iss(response); + std::string line; + + while (std::getline(iss, line)) + { + if (line.length() >= 4) + text += line.substr(4); + else + text += line; + + text += "\n"; + } + + return (text); +} + + +void SMTPTransport::readResponse(string& buffer) +{ + bool foundTerminator = false; + + buffer.clear(); + + for ( ; !foundTerminator ; ) + { + // Check whether the time-out delay is elapsed + if (m_timeoutHandler && m_timeoutHandler->isTimeOut()) + { + if (!m_timeoutHandler->handleTimeOut()) + throw exceptions::operation_timed_out(); + } + + // Receive data from the socket + string receiveBuffer; + m_socket->receive(receiveBuffer); + + if (receiveBuffer.empty()) // buffer is empty + { + platformDependant::getHandler()->wait(); + continue; + } + + // We have received data: reset the time-out counter + if (m_timeoutHandler) + m_timeoutHandler->resetTimeOut(); + + // Append the data to the response buffer + buffer += receiveBuffer; + + // Check for terminator string (and strip it if present) + if (buffer.length() >= 2 && buffer[buffer.length() - 1] == '\n') + { + string::size_type p = buffer.length() - 2; + bool end = false; + + for ( ; !end ; --p) + { + if (p == 0 || buffer[p] == '\n') + { + end = true; + + if (p + 4 < buffer.length()) + foundTerminator = true; + } + } + } + } + + // Remove [CR]LF at the end of the response + if (buffer.length() >= 2 && buffer[buffer.length() - 1] == '\n') + { + if (buffer[buffer.length() - 2] == '\r') + buffer.resize(buffer.length() - 2); + else + buffer.resize(buffer.length() - 1); + } +} + + + +// Service infos + +SMTPTransport::_infos SMTPTransport::sm_infos; + + +const serviceInfos& SMTPTransport::getInfosInstance() +{ + return (sm_infos); +} + + +const serviceInfos& SMTPTransport::getInfos() const +{ + return (sm_infos); +} + + +const port_t SMTPTransport::_infos::getDefaultPort() const +{ + return (25); +} + + +const string SMTPTransport::_infos::getPropertyPrefix() const +{ + return "transport.smtp."; +} + + +const std::vector SMTPTransport::_infos::getAvailableProperties() const +{ + std::vector list; + + // SMTP-specific options + list.push_back("options.need-authentication"); + + // Common properties + list.push_back("auth.username"); + list.push_back("auth.password"); + + list.push_back("server.address"); + list.push_back("server.port"); + list.push_back("server.socket-factory"); + + list.push_back("timeout.factory"); + + return (list); +} + + +} // smtp +} // messaging +} // vmime -- cgit