diff options
Diffstat (limited to 'src')
36 files changed, 9719 insertions, 36 deletions
diff --git a/src/base.cpp b/src/base.cpp index 8ccd1207..217b1fa5 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -40,7 +40,7 @@ #include "vmime/options.hpp" #if VMIME_HAVE_MESSAGING_FEATURES - #include "vmime/messaging/serviceFactory.hpp" + #include "vmime/net/serviceFactory.hpp" #endif @@ -143,7 +143,7 @@ public: textPartFactory::getInstance(); #if VMIME_HAVE_MESSAGING_FEATURES - messaging::serviceFactory::getInstance(); + net::serviceFactory::getInstance(); #endif } }; diff --git a/src/exception.cpp b/src/exception.cpp index 957b126c..5090e4fe 100644 --- a/src/exception.cpp +++ b/src/exception.cpp @@ -317,15 +317,15 @@ const char* system_error::name() const throw() { return "system_error"; } // -// messaging_exception +// net_exception // -messaging_exception::~messaging_exception() throw() {} -messaging_exception::messaging_exception(const string& what, const exception& other) +net_exception::~net_exception() throw() {} +net_exception::net_exception(const string& what, const exception& other) : exception(what, other) {} -exception* messaging_exception::clone() const { return new messaging_exception(*this); } -const char* messaging_exception::name() const throw() { return "messaging_exception"; } +exception* net_exception::clone() const { return new net_exception(*this); } +const char* net_exception::name() const throw() { return "net_exception"; } // @@ -334,7 +334,7 @@ const char* messaging_exception::name() const throw() { return "messaging_except connection_error::~connection_error() throw() {} connection_error::connection_error(const string& what, const exception& other) - : messaging_exception(what.empty() + : net_exception(what.empty() ? "Connection error." : "Connection error: '" + what + "'.", other) {} @@ -348,7 +348,7 @@ const char* connection_error::name() const throw() { return "connection_error"; connection_greeting_error::~connection_greeting_error() throw() {} connection_greeting_error::connection_greeting_error(const string& response, const exception& other) - : messaging_exception("Greeting error.", other), m_response(response) {} + : net_exception("Greeting error.", other), m_response(response) {} const string& connection_greeting_error::response() const { return (m_response); } @@ -362,7 +362,7 @@ const char* connection_greeting_error::name() const throw() { return "connection authentication_error::~authentication_error() throw() {} authentication_error::authentication_error(const string& response, const exception& other) - : messaging_exception("Authentication error.", other), m_response(response) {} + : net_exception("Authentication error.", other), m_response(response) {} const string& authentication_error::response() const { return (m_response); } @@ -376,7 +376,7 @@ const char* authentication_error::name() const throw() { return "authentication_ unsupported_option::~unsupported_option() throw() {} unsupported_option::unsupported_option(const exception& other) - : messaging_exception("Unsupported option.", other) {} + : net_exception("Unsupported option.", other) {} exception* unsupported_option::clone() const { return new unsupported_option(*this); } const char* unsupported_option::name() const throw() { return "unsupported_option"; } @@ -388,7 +388,7 @@ const char* unsupported_option::name() const throw() { return "unsupported_optio no_service_available::~no_service_available() throw() {} no_service_available::no_service_available(const string& proto, const exception& other) - : messaging_exception(proto.empty() + : net_exception(proto.empty() ? "No service available for this protocol." : "No service available for this protocol: '" + proto + "'.", other) {} @@ -402,7 +402,7 @@ const char* no_service_available::name() const throw() { return "no_service_avai illegal_state::~illegal_state() throw() {} illegal_state::illegal_state(const string& state, const exception& other) - : messaging_exception("Illegal state to accomplish the operation: '" + state + "'.", other) {} + : net_exception("Illegal state to accomplish the operation: '" + state + "'.", other) {} exception* illegal_state::clone() const { return new illegal_state(*this); } const char* illegal_state::name() const throw() { return "illegal_state"; } @@ -414,7 +414,7 @@ const char* illegal_state::name() const throw() { return "illegal_state"; } folder_not_found::~folder_not_found() throw() {} folder_not_found::folder_not_found(const exception& other) - : messaging_exception("Folder not found.", other) {} + : net_exception("Folder not found.", other) {} exception* folder_not_found::clone() const { return new folder_not_found(*this); } const char* folder_not_found::name() const throw() { return "folder_not_found"; } @@ -426,7 +426,7 @@ const char* folder_not_found::name() const throw() { return "folder_not_found"; message_not_found::~message_not_found() throw() {} message_not_found::message_not_found(const exception& other) - : messaging_exception("Message not found.", other) {} + : net_exception("Message not found.", other) {} exception* message_not_found::clone() const { return new message_not_found(*this); } const char* message_not_found::name() const throw() { return "message_not_found"; } @@ -438,7 +438,7 @@ const char* message_not_found::name() const throw() { return "message_not_found" operation_not_supported::~operation_not_supported() throw() {} operation_not_supported::operation_not_supported(const exception& other) - : messaging_exception("Operation not supported.", other) {} + : net_exception("Operation not supported.", other) {} exception* operation_not_supported::clone() const { return new operation_not_supported(*this); } const char* operation_not_supported::name() const throw() { return "operation_not_supported"; } @@ -450,7 +450,7 @@ const char* operation_not_supported::name() const throw() { return "operation_no operation_timed_out::~operation_timed_out() throw() {} operation_timed_out::operation_timed_out(const exception& other) - : messaging_exception("Operation timed out.", other) {} + : net_exception("Operation timed out.", other) {} exception* operation_timed_out::clone() const { return new operation_timed_out(*this); } const char* operation_timed_out::name() const throw() { return "operation_timed_out"; } @@ -462,7 +462,7 @@ const char* operation_timed_out::name() const throw() { return "operation_timed_ operation_cancelled::~operation_cancelled() throw() {} operation_cancelled::operation_cancelled(const exception& other) - : messaging_exception("Operation cancelled by the user.", other) {} + : net_exception("Operation cancelled by the user.", other) {} exception* operation_cancelled::clone() const { return new operation_cancelled(*this); } const char* operation_cancelled::name() const throw() { return "operation_cancelled"; } @@ -474,7 +474,7 @@ const char* operation_cancelled::name() const throw() { return "operation_cancel unfetched_object::~unfetched_object() throw() {} unfetched_object::unfetched_object(const exception& other) - : messaging_exception("Object not fetched.", other) {} + : net_exception("Object not fetched.", other) {} exception* unfetched_object::clone() const { return new unfetched_object(*this); } const char* unfetched_object::name() const throw() { return "unfetched_object"; } @@ -486,7 +486,7 @@ const char* unfetched_object::name() const throw() { return "unfetched_object"; not_connected::~not_connected() throw() {} not_connected::not_connected(const exception& other) - : messaging_exception("Not connected to a service.", other) {} + : net_exception("Not connected to a service.", other) {} exception* not_connected::clone() const { return new not_connected(*this); } const char* not_connected::name() const throw() { return "not_connected"; } @@ -498,7 +498,7 @@ const char* not_connected::name() const throw() { return "not_connected"; } already_connected::~already_connected() throw() {} already_connected::already_connected(const exception& other) - : messaging_exception("Already connected to a service. Disconnect and retry.", other) {} + : net_exception("Already connected to a service. Disconnect and retry.", other) {} exception* already_connected::clone() const { return new already_connected(*this); } const char* already_connected::name() const throw() { return "already_connected"; } @@ -510,7 +510,7 @@ const char* already_connected::name() const throw() { return "already_connected" illegal_operation::~illegal_operation() throw() {} illegal_operation::illegal_operation(const string& msg, const exception& other) - : messaging_exception(msg.empty() + : net_exception(msg.empty() ? "Illegal operation." : "Illegal operation: " + msg + ".", other @@ -527,7 +527,7 @@ const char* illegal_operation::name() const throw() { return "illegal_operation" command_error::~command_error() throw() {} command_error::command_error(const string& command, const string& response, const string& desc, const exception& other) - : messaging_exception(desc.empty() + : net_exception(desc.empty() ? "Error while executing command '" + command + "'." : "Error while executing command '" + command + "': " + desc + ".", other @@ -548,7 +548,7 @@ const char* command_error::name() const throw() { return "command_error"; } invalid_response::~invalid_response() throw() {} invalid_response::invalid_response(const string& command, const string& response, const exception& other) - : messaging_exception(command.empty() + : net_exception(command.empty() ? "Received invalid response." : "Received invalid response for command '" + command + "'.", other @@ -569,7 +569,7 @@ const char* invalid_response::name() const throw() { return "invalid_response"; partial_fetch_not_supported::~partial_fetch_not_supported() throw() {} partial_fetch_not_supported::partial_fetch_not_supported(const exception& other) - : messaging_exception("Partial fetch not supported.", other) {} + : net_exception("Partial fetch not supported.", other) {} exception* partial_fetch_not_supported::clone() const { return new partial_fetch_not_supported(*this); } const char* partial_fetch_not_supported::name() const throw() { return "partial_fetch_not_supported"; } @@ -581,7 +581,7 @@ const char* partial_fetch_not_supported::name() const throw() { return "partial_ malformed_url::~malformed_url() throw() {} malformed_url::malformed_url(const string& error, const exception& other) - : messaging_exception("Malformed URL: " + error + ".", other) {} + : net_exception("Malformed URL: " + error + ".", other) {} exception* malformed_url::clone() const { return new malformed_url(*this); } const char* malformed_url::name() const throw() { return "malformed_url"; } @@ -593,7 +593,7 @@ const char* malformed_url::name() const throw() { return "malformed_url"; } invalid_folder_name::~invalid_folder_name() throw() {} invalid_folder_name::invalid_folder_name(const string& error, const exception& other) - : messaging_exception(error.empty() + : net_exception(error.empty() ? "Invalid folder name: " + error + "." : "Invalid folder name.", other) {} diff --git a/src/net/authHelper.cpp b/src/net/authHelper.cpp new file mode 100644 index 00000000..e10b47ee --- /dev/null +++ b/src/net/authHelper.cpp @@ -0,0 +1,105 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/authHelper.hpp" + +#include "vmime/config.hpp" +#include "vmime/utility/md5.hpp" + + +namespace vmime { +namespace net { + + +// +// This code is based on the Sample Code published in the Appendix of +// the RFC-2104: "HMAC: Keyed-Hashing for Message Authentication". +// + +void hmac_md5(const string& text, const string& key, string& hexDigest) +{ + vmime_uint8 digest[16]; + + unsigned char ipad[65]; // inner padding - key XORd with ipad + unsigned char opad[65]; // outer padding - key XORd with opad + + unsigned char tkey[16]; + int tkeyLen; + + // If key is longer than 64 bytes reset it to key = MD5(key) + if (key.length() > 64) + { + utility::md5 keyMD5; + keyMD5.update(reinterpret_cast <const vmime_uint8*>(key.data()), key.length()); + + std::copy(keyMD5.hash(), keyMD5.hash() + 16, tkey); + tkeyLen = 16; + } + else + { + std::copy(key.begin(), key.end(), tkey); + tkeyLen = key.length(); + } + + // + // the HMAC_MD5 transform looks like: + // + // MD5(K XOR opad, MD5(K XOR ipad, text)) + // + // where K is an n byte key + // ipad is the byte 0x36 repeated 64 times + // + // opad is the byte 0x5c repeated 64 times + // and text is the data being protected + // + + // Start out by storing key in pads + std::fill(ipad, ipad + sizeof(ipad), 0); + std::fill(opad, opad + sizeof(opad), 0); + + std::copy(tkey, tkey + tkeyLen, ipad); + std::copy(tkey, tkey + tkeyLen, opad); + + // XOR key with ipad and opad values + for (int i = 0 ; i < 64 ; ++i) + { + ipad[i] ^= 0x36; + opad[i] ^= 0x5c; + } + + // Perform inner MD5 + utility::md5 innerMD5; + innerMD5.update(ipad, 64); + innerMD5.update(text); + + std::copy(innerMD5.hash(), innerMD5.hash() + 16, digest); + + // Perform outer MD5 + utility::md5 outerMD5; + outerMD5.update(opad, 64); + outerMD5.update(digest, 16); + + //std::copy(outerMD5.hash(), outerMD5.hash() + 16, digest); + + hexDigest = outerMD5.hex(); +} + + +} // net +} // vmime diff --git a/src/net/authenticationInfos.cpp b/src/net/authenticationInfos.cpp new file mode 100644 index 00000000..e5a93fb2 --- /dev/null +++ b/src/net/authenticationInfos.cpp @@ -0,0 +1,52 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/authenticationInfos.hpp" + + +namespace vmime { +namespace net { + + +authenticationInfos::authenticationInfos(const string& username, const string& password) + : m_username(username), m_password(password) +{ +} + + +authenticationInfos::authenticationInfos(const authenticationInfos& infos) + : object(), m_username(infos.m_username), m_password(infos.m_password) +{ +} + + +const string& authenticationInfos::getUsername() const +{ + return (m_username); +} + + +const string& authenticationInfos::getPassword() const +{ + return (m_password); +} + + +} // net +} // vmime diff --git a/src/net/authenticator.cpp b/src/net/authenticator.cpp new file mode 100644 index 00000000..d894915c --- /dev/null +++ b/src/net/authenticator.cpp @@ -0,0 +1,33 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/authenticator.hpp" + + +namespace vmime { +namespace net { + + +authenticator::~authenticator() +{ +} + + +} // net +} // vmime diff --git a/src/net/builtinServices.inl b/src/net/builtinServices.inl new file mode 100644 index 00000000..498d41eb --- /dev/null +++ b/src/net/builtinServices.inl @@ -0,0 +1,56 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#ifndef VMIME_BUILDING_DOC + + +#define REGISTER_SERVICE(p_class, p_name) \ + vmime::net::service::initializer <vmime::net::p_class> p_name(#p_name) + + +#if VMIME_BUILTIN_MESSAGING_PROTO_POP3 + #include "vmime/net/pop3/POP3Store.hpp" + REGISTER_SERVICE(pop3::POP3Store, pop3); +#endif + + +#if VMIME_BUILTIN_MESSAGING_PROTO_SMTP + #include "vmime/net/smtp/SMTPTransport.hpp" + REGISTER_SERVICE(smtp::SMTPTransport, smtp); +#endif + + +#if VMIME_BUILTIN_MESSAGING_PROTO_IMAP + #include "vmime/net/imap/IMAPStore.hpp" + REGISTER_SERVICE(imap::IMAPStore, imap); +#endif + + +#if VMIME_BUILTIN_MESSAGING_PROTO_MAILDIR + #include "vmime/net/maildir/maildirStore.hpp" + REGISTER_SERVICE(maildir::maildirStore, maildir); +#endif + +#if VMIME_BUILTIN_MESSAGING_PROTO_SENDMAIL + #include "vmime/net/sendmail/sendmailTransport.hpp" + REGISTER_SERVICE(sendmail::sendmailTransport, sendmail); +#endif + + +#endif // VMIME_BUILDING_DOC diff --git a/src/net/defaultAuthenticator.cpp b/src/net/defaultAuthenticator.cpp new file mode 100644 index 00000000..59835b64 --- /dev/null +++ b/src/net/defaultAuthenticator.cpp @@ -0,0 +1,43 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/defaultAuthenticator.hpp" +#include "vmime/net/session.hpp" + + +namespace vmime { +namespace net { + + +defaultAuthenticator::defaultAuthenticator(weak_ref <session> sess, const string& prefix) + : m_session(sess), m_prefix(prefix) +{ +} + + +const authenticationInfos defaultAuthenticator::requestAuthInfos() const +{ + return (authenticationInfos + (m_session->getProperties()[m_prefix + "auth.username"], + m_session->getProperties()[m_prefix + "auth.password"])); +} + + +} // net +} // vmime diff --git a/src/net/events.cpp b/src/net/events.cpp new file mode 100644 index 00000000..d1818f03 --- /dev/null +++ b/src/net/events.cpp @@ -0,0 +1,111 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/events.hpp" +#include "vmime/net/folder.hpp" + +#include <algorithm> + + +namespace vmime { +namespace net { +namespace events { + + +// +// messageCountEvent +// + +messageCountEvent::messageCountEvent + (ref <folder> folder, const Types type, const std::vector <int>& nums) + : m_folder(folder), m_type(type) +{ + m_nums.resize(nums.size()); + std::copy(nums.begin(), nums.end(), m_nums.begin()); +} + + +ref <const folder> messageCountEvent::getFolder() const { return (m_folder); } +const messageCountEvent::Types messageCountEvent::getType() const { return (m_type); } +const std::vector <int>& messageCountEvent::getNumbers() const { return (m_nums); } + + +void messageCountEvent::dispatch(messageCountListener* listener) const +{ + if (m_type == TYPE_ADDED) + listener->messagesAdded(*this); + else + listener->messagesRemoved(*this); +} + + +// +// messageChangedEvent +// + +messageChangedEvent::messageChangedEvent + (ref <folder> folder, const Types type, const std::vector <int>& nums) + : m_folder(folder), m_type(type) +{ + m_nums.resize(nums.size()); + std::copy(nums.begin(), nums.end(), m_nums.begin()); +} + + +ref <const folder> messageChangedEvent::getFolder() const { return (m_folder); } +const messageChangedEvent::Types messageChangedEvent::getType() const { return (m_type); } +const std::vector <int>& messageChangedEvent::getNumbers() const { return (m_nums); } + + +void messageChangedEvent::dispatch(messageChangedListener* listener) const +{ + listener->messageChanged(*this); +} + + +// +// folderEvent +// + +folderEvent::folderEvent + (ref <folder> folder, const Types type, + const utility::path& oldPath, const utility::path& newPath) + : m_folder(folder), m_type(type), m_oldPath(oldPath), m_newPath(newPath) +{ +} + + +ref <const folder> folderEvent::getFolder() const { return (m_folder); } +const folderEvent::Types folderEvent::getType() const { return (m_type); } + + +void folderEvent::dispatch(folderListener* listener) const +{ + switch (m_type) + { + case TYPE_CREATED: listener->folderCreated(*this); break; + case TYPE_RENAMED: listener->folderRenamed(*this); break; + case TYPE_DELETED: listener->folderDeleted(*this); break; + } +} + + +} // events +} // net +} // vmime diff --git a/src/net/folder.cpp b/src/net/folder.cpp new file mode 100644 index 00000000..5172669b --- /dev/null +++ b/src/net/folder.cpp @@ -0,0 +1,96 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/folder.hpp" + +#include <algorithm> + + +namespace vmime { +namespace net { + + +void folder::addMessageChangedListener(events::messageChangedListener* l) +{ + m_messageChangedListeners.push_back(l); +} + + +void folder::removeMessageChangedListener(events::messageChangedListener* l) +{ + std::remove(m_messageChangedListeners.begin(), m_messageChangedListeners.end(), l); +} + + +void folder::notifyMessageChanged(const events::messageChangedEvent& event) +{ + for (std::list <events::messageChangedListener*>::iterator + it = m_messageChangedListeners.begin() ; it != m_messageChangedListeners.end() ; ++it) + { + event.dispatch(*it); + } +} + + +void folder::addMessageCountListener(events::messageCountListener* l) +{ + m_messageCountListeners.push_back(l); +} + + +void folder::removeMessageCountListener(events::messageCountListener* l) +{ + std::remove(m_messageCountListeners.begin(), m_messageCountListeners.end(), l); +} + + +void folder::notifyMessageCount(const events::messageCountEvent& event) +{ + for (std::list <events::messageCountListener*>::iterator + it = m_messageCountListeners.begin() ; it != m_messageCountListeners.end() ; ++it) + { + event.dispatch(*it); + } +} + + +void folder::addFolderListener(events::folderListener* l) +{ + m_folderListeners.push_back(l); +} + + +void folder::removeFolderListener(events::folderListener* l) +{ + std::remove(m_folderListeners.begin(), m_folderListeners.end(), l); +} + + +void folder::notifyFolder(const events::folderEvent& event) +{ + for (std::list <events::folderListener*>::iterator + it = m_folderListeners.begin() ; it != m_folderListeners.end() ; ++it) + { + event.dispatch(*it); + } +} + + +} // net +} // vmime diff --git a/src/net/imap/IMAPConnection.cpp b/src/net/imap/IMAPConnection.cpp new file mode 100644 index 00000000..5191dc94 --- /dev/null +++ b/src/net/imap/IMAPConnection.cpp @@ -0,0 +1,313 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/imap/IMAPTag.hpp" +#include "vmime/net/imap/IMAPConnection.hpp" +#include "vmime/net/imap/IMAPUtils.hpp" +#include "vmime/net/imap/IMAPStore.hpp" + +#include "vmime/exception.hpp" +#include "vmime/platformDependant.hpp" + +#include <sstream> + + +// Helpers for service properties +#define GET_PROPERTY(type, prop) \ + (m_store->getInfos().getPropertyValue <type>(getSession(), \ + dynamic_cast <const IMAPStore::_infos&>(m_store->getInfos()).getProperties().prop)) +#define HAS_PROPERTY(prop) \ + (m_store->getInfos().hasProperty(getSession(), \ + dynamic_cast <const IMAPStore::_infos&>(m_store->getInfos()).getProperties().prop)) + + +namespace vmime { +namespace net { +namespace imap { + + +IMAPConnection::IMAPConnection(weak_ref <IMAPStore> store, ref <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(); +} + + +void IMAPConnection::connect() +{ + if (isConnected()) + throw exceptions::already_connected(); + + m_state = STATE_NONE; + m_hierarchySeparator = '\0'; + + const string address = GET_PROPERTY(string, PROPERTY_SERVER_ADDRESS); + const port_t port = GET_PROPERTY(port_t, PROPERTY_SERVER_PORT); + + // Create the time-out handler + if (HAS_PROPERTY(PROPERTY_TIMEOUT_FACTORY)) + { + timeoutHandlerFactory* tof = platformDependant::getHandler()-> + getTimeoutHandlerFactory(GET_PROPERTY(string, PROPERTY_TIMEOUT_FACTORY)); + + m_timeoutHandler = tof->create(); + } + + // Create and connect the socket + socketFactory* sf = platformDependant::getHandler()-> + getSocketFactory(GET_PROPERTY(string, PROPERTY_SERVER_SOCKETFACTORY)); + + m_socket = sf->create(); + m_socket->connect(address, port); + + + m_tag = vmime::create <IMAPTag>(); + m_parser = vmime::create <IMAPParser>(m_tag, m_socket, m_timeoutHandler); + + + setState(STATE_NON_AUTHENTICATED); + + + // Connection greeting + // + // eg: C: <connection to server> + // --- S: * OK mydomain.org IMAP4rev1 v12.256 server ready + + utility::auto_ptr <IMAPParser::greeting> 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 <IMAPParser::response> 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() +{ + if (isConnected()) + { + send(true, "LOGOUT", true); + + m_socket->disconnect(); + m_socket = NULL; + } + + m_timeoutHandler = NULL; + + m_state = STATE_LOGOUT; +} + + +void IMAPConnection::initHierarchySeparator() +{ + send(true, "LIST \"\" \"\"", true); + + vmime::utility::auto_ptr <IMAPParser::response> 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 <IMAPParser::continue_req_or_response_data*>& 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 <const IMAPParser::response_data*>(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)); +} + + +const IMAPConnection::ProtocolStates IMAPConnection::state() const +{ + return (m_state); +} + + +void IMAPConnection::setState(const ProtocolStates state) +{ + m_state = state; +} + +const char IMAPConnection::hierarchySeparator() const +{ + return (m_hierarchySeparator); +} + + +ref <const IMAPTag> IMAPConnection::getTag() const +{ + return (m_tag); +} + + +ref <const IMAPParser> IMAPConnection::getParser() const +{ + return (m_parser); +} + + +weak_ref <const IMAPStore> IMAPConnection::getStore() const +{ + return (m_store); +} + + +weak_ref <IMAPStore> IMAPConnection::getStore() +{ + return (m_store); +} + + +ref <session> IMAPConnection::getSession() +{ + return (m_store->getSession()); +} + + +} // imap +} // net +} // vmime diff --git a/src/net/imap/IMAPFolder.cpp b/src/net/imap/IMAPFolder.cpp new file mode 100644 index 00000000..ea0ddfd9 --- /dev/null +++ b/src/net/imap/IMAPFolder.cpp @@ -0,0 +1,1608 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/imap/IMAPFolder.hpp" + +#include "vmime/net/imap/IMAPStore.hpp" +#include "vmime/net/imap/IMAPParser.hpp" +#include "vmime/net/imap/IMAPMessage.hpp" +#include "vmime/net/imap/IMAPUtils.hpp" +#include "vmime/net/imap/IMAPConnection.hpp" + +#include "vmime/message.hpp" + +#include "vmime/exception.hpp" +#include "vmime/utility/smartPtr.hpp" + +#include <algorithm> +#include <sstream> + + +namespace vmime { +namespace net { +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) + { + m_connection = NULL; + 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 + ref <IMAPConnection> connection = + vmime::create <IMAPConnection>(m_store, m_store->oneTimeAuthenticator()); + + try + { + connection->connect(); + + // Emit the "SELECT" command + // + // Example: C: A142 SELECT INBOX + // S: * 172 EXISTS + // S: * 1 RECENT + // S: * OK [UNSEEN 12] Message 12 is first unseen + // S: * OK [UIDVALIDITY 3857529045] UIDs valid + // S: * FLAGS (\Answered \Flagged \Deleted \Seen \Draft) + // S: * OK [PERMANENTFLAGS (\Deleted \Seen \*)] Limited + // S: A142 OK [READ-WRITE] SELECT completed + + std::ostringstream oss; + + if (mode == MODE_READ_ONLY) + oss << "EXAMINE "; + else + oss << "SELECT "; + + oss << IMAPUtils::quoteString(IMAPUtils::pathToString + (connection->hierarchySeparator(), getFullPath())); + + connection->send(true, oss.str(), true); + + // Read the response + utility::auto_ptr <IMAPParser::response> resp(connection->readResponse()); + + if (resp->isBad() || resp->response_done()->response_tagged()-> + resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) + { + throw exceptions::command_error("SELECT", + connection->getParser()->lastLine(), "bad response"); + } + + const std::vector <IMAPParser::continue_req_or_response_data*>& respDataList = + resp->continue_req_or_response_data(); + + for (std::vector <IMAPParser::continue_req_or_response_data*>::const_iterator + it = respDataList.begin() ; it != respDataList.end() ; ++it) + { + if ((*it)->response_data() == NULL) + { + throw exceptions::command_error("SELECT", + connection->getParser()->lastLine(), "invalid response"); + } + + const IMAPParser::response_data* responseData = (*it)->response_data(); + + // OK Untagged responses: UNSEEN, PERMANENTFLAGS, UIDVALIDITY (optional) + if (responseData->resp_cond_state()) + { + const IMAPParser::resp_text_code* code = + responseData->resp_cond_state()->resp_text()->resp_text_code(); + + if (code != NULL) + { + switch (code->type()) + { + case IMAPParser::resp_text_code::UIDVALIDITY: + + m_uidValidity = code->nz_number()->value(); + break; + + default: + + break; + } + } + } + // Untagged responses: FLAGS, EXISTS, RECENT (required) + else if (responseData->mailbox_data()) + { + switch (responseData->mailbox_data()->type()) + { + default: break; + + case IMAPParser::mailbox_data::FLAGS: + { + m_type = IMAPUtils::folderTypeFromFlags + (responseData->mailbox_data()->mailbox_flag_list()); + + m_flags = IMAPUtils::folderFlagsFromFlags + (responseData->mailbox_data()->mailbox_flag_list()); + + break; + } + case IMAPParser::mailbox_data::EXISTS: + { + m_messageCount = responseData->mailbox_data()->number()->value(); + break; + } + case IMAPParser::mailbox_data::RECENT: + { + // TODO + break; + } + + } + } + } + + // Check for access mode (read-only or read-write) + const IMAPParser::resp_text_code* respTextCode = resp->response_done()-> + response_tagged()->resp_cond_state()->resp_text()->resp_text_code(); + + if (respTextCode) + { + const int openMode = + (respTextCode->type() == IMAPParser::resp_text_code::READ_WRITE) + ? MODE_READ_WRITE : MODE_READ_ONLY; + + if (failIfModeIsNotAvailable && + mode == MODE_READ_WRITE && openMode == MODE_READ_ONLY) + { + throw exceptions::operation_not_supported(); + } + } + + + m_connection = connection; + m_open = true; + m_mode = mode; + } + catch (std::exception&) + { + 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"); + + ref <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(); +} + + +void IMAPFolder::onClose() +{ + for (std::vector <IMAPMessage*>::iterator it = m_messages.begin() ; + it != m_messages.end() ; ++it) + { + (*it)->onFolderClosed(); + } + + m_messages.clear(); +} + + +void IMAPFolder::create(const int type) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (isOpen()) + throw exceptions::illegal_state("Folder is open"); + else if (exists()) + throw exceptions::illegal_state("Folder already exists"); + else if (!m_store->isValidFolderName(m_name)) + throw exceptions::invalid_folder_name(); + + // Emit the "CREATE" command + // + // Example: C: A003 CREATE owatagusiam/ + // S: A003 OK CREATE completed + // C: A004 CREATE owatagusiam/blurdybloop + // S: A004 OK CREATE completed + + string mailbox = IMAPUtils::pathToString + (m_connection->hierarchySeparator(), getFullPath()); + + if (type & TYPE_CONTAINS_FOLDERS) + mailbox += m_connection->hierarchySeparator(); + + std::ostringstream oss; + oss << "CREATE " << IMAPUtils::quoteString(mailbox); + + m_connection->send(true, oss.str(), true); + + + utility::auto_ptr <IMAPParser::response> resp(m_connection->readResponse()); + + if (resp->isBad() || resp->response_done()->response_tagged()-> + resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) + { + throw exceptions::command_error("CREATE", + m_connection->getParser()->lastLine(), "bad response"); + } + + // Notify folder created + events::folderEvent event + (thisRef().dynamicCast <folder>(), + events::folderEvent::TYPE_CREATED, m_path, m_path); + + notifyFolder(event); +} + + +const bool IMAPFolder::exists() +{ + if (!isOpen() && !m_store) + throw exceptions::illegal_state("Store disconnected"); + + return (testExistAndGetType() != TYPE_UNDEFINED); +} + + +const int IMAPFolder::testExistAndGetType() +{ + m_type = TYPE_UNDEFINED; + + // To test whether a folder exists, we simple list it using + // the "LIST" command, and there should be one unique mailbox + // with this name... + // + // Eg. Test whether '/foo/bar' exists + // + // C: a005 list "" foo/bar + // S: * LIST (\NoSelect) "/" foo/bar + // S: a005 OK LIST completed + // + // ==> OK, exists + // + // Test whether '/foo/bar/zap' exists + // + // C: a005 list "" foo/bar/zap + // S: a005 OK LIST completed + // + // ==> NO, does not exist + + std::ostringstream oss; + oss << "LIST \"\" "; + oss << IMAPUtils::quoteString(IMAPUtils::pathToString + (m_connection->hierarchySeparator(), getFullPath())); + + m_connection->send(true, oss.str(), true); + + + utility::auto_ptr <IMAPParser::response> resp(m_connection->readResponse()); + + if (resp->isBad() || resp->response_done()->response_tagged()-> + resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) + { + throw exceptions::command_error("LIST", + m_connection->getParser()->lastLine(), "bad response"); + } + + // Check whether the result mailbox list contains this folder + const std::vector <IMAPParser::continue_req_or_response_data*>& respDataList = + resp->continue_req_or_response_data(); + + for (std::vector <IMAPParser::continue_req_or_response_data*>::const_iterator + it = respDataList.begin() ; it != respDataList.end() ; ++it) + { + if ((*it)->response_data() == NULL) + { + throw exceptions::command_error("LIST", + m_connection->getParser()->lastLine(), "invalid response"); + } + + const IMAPParser::mailbox_data* mailboxData = + (*it)->response_data()->mailbox_data(); + + // We are only interested in responses of type "LIST" + if (mailboxData != NULL && mailboxData->type() == IMAPParser::mailbox_data::LIST) + { + // Get the folder type/flags at the same time + m_type = IMAPUtils::folderTypeFromFlags + (mailboxData->mailbox_list()->mailbox_flag_list()); + + m_flags = IMAPUtils::folderFlagsFromFlags + (mailboxData->mailbox_list()->mailbox_flag_list()); + } + } + + return (m_type); +} + + +const bool IMAPFolder::isOpen() const +{ + return (m_open); +} + + +ref <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 vmime::create <IMAPMessage>(this, num); +} + + +std::vector <ref <message> > IMAPFolder::getMessages(const int from, const int to) +{ + const int messageCount = getMessageCount(); + const int to2 = (to == -1 ? messageCount : to); + + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + else if (to2 < from || from < 1 || to2 < 1 || from > messageCount || to2 > messageCount) + throw exceptions::message_not_found(); + + std::vector <ref <message> > v; + + for (int i = from ; i <= to2 ; ++i) + v.push_back(vmime::create <IMAPMessage>(this, i)); + + return (v); +} + + +std::vector <ref <message> > IMAPFolder::getMessages(const std::vector <int>& nums) +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + std::vector <ref <message> > v; + + for (std::vector <int>::const_iterator it = nums.begin() ; it != nums.end() ; ++it) + v.push_back(vmime::create <IMAPMessage>(this, *it)); + + return (v); +} + + +const int IMAPFolder::getMessageCount() +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + return (m_messageCount); +} + + +ref <folder> IMAPFolder::getFolder(const folder::path::component& name) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + + return vmime::create <IMAPFolder>(m_path / name, m_store); +} + + +std::vector <ref <folder> > IMAPFolder::getFolders(const bool recursive) +{ + if (!isOpen() && !m_store) + throw exceptions::illegal_state("Store disconnected"); + + // Eg. List folders in '/foo/bar' + // + // C: a005 list "foo/bar" * + // S: * LIST (\NoSelect) "/" foo/bar + // S: * LIST (\NoInferiors) "/" foo/bar/zap + // S: a005 OK LIST completed + + std::ostringstream oss; + oss << "LIST "; + + const string pathString = IMAPUtils::pathToString + (m_connection->hierarchySeparator(), getFullPath()); + + if (recursive) + { + oss << IMAPUtils::quoteString(pathString); + oss << " *"; + } + else + { + if (pathString.empty()) // don't add sep for root folder + oss << "\"\""; + else + oss << IMAPUtils::quoteString(pathString + m_connection->hierarchySeparator()); + + oss << " %"; + } + + m_connection->send(true, oss.str(), true); + + + utility::auto_ptr <IMAPParser::response> resp(m_connection->readResponse()); + + if (resp->isBad() || resp->response_done()->response_tagged()-> + resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) + { + throw exceptions::command_error("LIST", m_connection->getParser()->lastLine(), "bad response"); + } + + const std::vector <IMAPParser::continue_req_or_response_data*>& respDataList = + resp->continue_req_or_response_data(); + + + std::vector <ref <folder> > v; + + for (std::vector <IMAPParser::continue_req_or_response_data*>::const_iterator + it = respDataList.begin() ; it != respDataList.end() ; ++it) + { + if ((*it)->response_data() == NULL) + { + throw exceptions::command_error("LIST", + m_connection->getParser()->lastLine(), "invalid response"); + } + + const IMAPParser::mailbox_data* mailboxData = + (*it)->response_data()->mailbox_data(); + + if (mailboxData == NULL || mailboxData->type() != IMAPParser::mailbox_data::LIST) + continue; + + // Get folder path + const class IMAPParser::mailbox* mailbox = + mailboxData->mailbox_list()->mailbox(); + + folder::path path = IMAPUtils::stringToPath + (mailboxData->mailbox_list()->quoted_char(), mailbox->name()); + + if (recursive || m_path.isDirectParentOf(path)) + { + // Append folder to list + const class IMAPParser::mailbox_flag_list* mailbox_flag_list = + mailboxData->mailbox_list()->mailbox_flag_list(); + + v.push_back(vmime::create <IMAPFolder>(path, m_store, + IMAPUtils::folderTypeFromFlags(mailbox_flag_list), + IMAPUtils::folderFlagsFromFlags(mailbox_flag_list))); + } + } + + return (v); +} + + +void IMAPFolder::fetchMessages(std::vector <ref <message> >& msg, const int options, + utility::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 <ref <message> >::iterator it = msg.begin() ; + it != msg.end() ; ++it) + { + (*it).dynamicCast <IMAPMessage>()->fetch(this, options); + + if (progress) + progress->progress(++current, total); + } + + if (progress) + progress->stop(total); +} + + +void IMAPFolder::fetchMessage(ref <message> msg, const int options) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + msg.dynamicCast <IMAPMessage>()->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 | + FETCH_IMPORTANCE); +} + + +ref <folder> IMAPFolder::getParent() +{ + if (m_path.isEmpty()) + return NULL; + else + return vmime::create <IMAPFolder>(m_path.getParent(), m_store); +} + + +weak_ref <const store> IMAPFolder::getStore() const +{ + return (m_store); +} + + +weak_ref <store> IMAPFolder::getStore() +{ + return (m_store); +} + + +void IMAPFolder::registerMessage(IMAPMessage* msg) +{ + m_messages.push_back(msg); +} + + +void IMAPFolder::unregisterMessage(IMAPMessage* msg) +{ + std::vector <IMAPMessage*>::iterator it = + std::find(m_messages.begin(), m_messages.end(), msg); + + if (it != m_messages.end()) + m_messages.erase(it); +} + + +void IMAPFolder::onStoreDisconnected() +{ + m_store = NULL; +} + + +void IMAPFolder::deleteMessage(const int num) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + else if (m_mode == MODE_READ_ONLY) + throw exceptions::illegal_state("Folder is read-only"); + + // Build the request text + std::ostringstream command; + command << "STORE " << num << " +FLAGS.SILENT (\\Deleted)"; + + // Send the request + m_connection->send(true, command.str(), true); + + // Get the response + utility::auto_ptr <IMAPParser::response> resp(m_connection->readResponse()); + + if (resp->isBad() || resp->response_done()->response_tagged()-> + resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) + { + throw exceptions::command_error("STORE", + m_connection->getParser()->lastLine(), "bad response"); + } + + // Update local flags + for (std::vector <IMAPMessage*>::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + if ((*it)->getNumber() == num && + (*it)->m_flags != message::FLAG_UNDEFINED) + { + (*it)->m_flags |= message::FLAG_DELETED; + } + } + + // Notify message flags changed + std::vector <int> nums; + nums.push_back(num); + + events::messageChangedEvent event + (thisRef().dynamicCast <folder>(), + events::messageChangedEvent::TYPE_FLAGS, nums); + + notifyMessageChanged(event); +} + + +void IMAPFolder::deleteMessages(const int from, const int to) +{ + if (from < 1 || (to < from && to != -1)) + throw exceptions::invalid_argument(); + + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + else if (m_mode == MODE_READ_ONLY) + throw exceptions::illegal_state("Folder is read-only"); + + // Build the request text + std::ostringstream command; + command << "STORE " << from << ":"; + + if (to == -1) command << m_messageCount; + else command << to; + + command << " +FLAGS.SILENT (\\Deleted)"; + + // Send the request + m_connection->send(true, command.str(), true); + + // Get the response + utility::auto_ptr <IMAPParser::response> resp(m_connection->readResponse()); + + if (resp->isBad() || resp->response_done()->response_tagged()-> + resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) + { + throw exceptions::command_error("STORE", + m_connection->getParser()->lastLine(), "bad response"); + } + + // Update local flags + const int to2 = (to == -1) ? m_messageCount : to; + const int count = to - from + 1; + + for (std::vector <IMAPMessage*>::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + if ((*it)->getNumber() >= from && (*it)->getNumber() <= to2 && + (*it)->m_flags != message::FLAG_UNDEFINED) + { + (*it)->m_flags |= message::FLAG_DELETED; + } + } + + // Notify message flags changed + std::vector <int> nums; + nums.resize(count); + + for (int i = from, j = 0 ; i <= to2 ; ++i, ++j) + nums[j] = i; + + events::messageChangedEvent event + (thisRef().dynamicCast <folder>(), + events::messageChangedEvent::TYPE_FLAGS, nums); + + notifyMessageChanged(event); +} + + +void IMAPFolder::deleteMessages(const std::vector <int>& nums) +{ + if (nums.empty()) + throw exceptions::invalid_argument(); + + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + else if (m_mode == MODE_READ_ONLY) + throw exceptions::illegal_state("Folder is read-only"); + + // Sort the list of message numbers + std::vector <int> list; + + list.resize(nums.size()); + std::copy(nums.begin(), nums.end(), list.begin()); + + std::sort(list.begin(), list.end()); + + // Build the request text + std::ostringstream command; + command << "STORE "; + command << IMAPUtils::listToSet(list, m_messageCount, true); + command << " +FLAGS.SILENT (\\Deleted)"; + + // Send the request + m_connection->send(true, command.str(), true); + + // Get the response + utility::auto_ptr <IMAPParser::response> resp(m_connection->readResponse()); + + if (resp->isBad() || resp->response_done()->response_tagged()-> + resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) + { + throw exceptions::command_error("STORE", + m_connection->getParser()->lastLine(), "bad response"); + } + + // Update local flags + for (std::vector <IMAPMessage*>::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + if (std::binary_search(list.begin(), list.end(), (*it)->getNumber())) + { + if ((*it)->m_flags != message::FLAG_UNDEFINED) + (*it)->m_flags |= message::FLAG_DELETED; + } + } + + // Notify message flags changed + events::messageChangedEvent event + (thisRef().dynamicCast <folder>(), + events::messageChangedEvent::TYPE_FLAGS, list); + + notifyMessageChanged(event); +} + + +void IMAPFolder::setMessageFlags(const int from, const int to, const int flags, const int mode) +{ + if (from < 1 || (to < from && to != -1)) + throw exceptions::invalid_argument(); + + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + else if (m_mode == MODE_READ_ONLY) + throw exceptions::illegal_state("Folder is read-only"); + + std::ostringstream oss; + + if (to == -1) + oss << from << ":*"; + else + oss << from << ":" << to; + + setMessageFlags(oss.str(), flags, mode); + + // Update local flags + const int to2 = (to == -1) ? m_messageCount : to; + const int count = to - from + 1; + + switch (mode) + { + case message::FLAG_MODE_ADD: + { + for (std::vector <IMAPMessage*>::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + if ((*it)->getNumber() >= from && (*it)->getNumber() <= to2 && + (*it)->m_flags != message::FLAG_UNDEFINED) + { + (*it)->m_flags |= flags; + } + } + + break; + } + case message::FLAG_MODE_REMOVE: + { + for (std::vector <IMAPMessage*>::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + if ((*it)->getNumber() >= from && (*it)->getNumber() <= to2 && + (*it)->m_flags != message::FLAG_UNDEFINED) + { + (*it)->m_flags &= ~flags; + } + } + + break; + } + default: + case message::FLAG_MODE_SET: + { + for (std::vector <IMAPMessage*>::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + if ((*it)->getNumber() >= from && (*it)->getNumber() <= to2 && + (*it)->m_flags != message::FLAG_UNDEFINED) + { + (*it)->m_flags = flags; + } + } + + break; + } + + } + + // Notify message flags changed + std::vector <int> nums; + nums.resize(count); + + for (int i = from, j = 0 ; i <= to2 ; ++i, ++j) + nums[j] = i; + + events::messageChangedEvent event + (thisRef().dynamicCast <folder>(), + events::messageChangedEvent::TYPE_FLAGS, nums); + + notifyMessageChanged(event); +} + + +void IMAPFolder::setMessageFlags(const std::vector <int>& nums, const int flags, const int mode) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + else if (m_mode == MODE_READ_ONLY) + throw exceptions::illegal_state("Folder is read-only"); + + // Sort the list of message numbers + std::vector <int> list; + + list.resize(nums.size()); + std::copy(nums.begin(), nums.end(), list.begin()); + + std::sort(list.begin(), list.end()); + + // Delegates call + setMessageFlags(IMAPUtils::listToSet(list, m_messageCount, true), flags, mode); + + // Update local flags + switch (mode) + { + case message::FLAG_MODE_ADD: + { + for (std::vector <IMAPMessage*>::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + if (std::binary_search(list.begin(), list.end(), (*it)->getNumber()) && + (*it)->m_flags != message::FLAG_UNDEFINED) + { + (*it)->m_flags |= flags; + } + } + + break; + } + case message::FLAG_MODE_REMOVE: + { + for (std::vector <IMAPMessage*>::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + if (std::binary_search(list.begin(), list.end(), (*it)->getNumber()) && + (*it)->m_flags != message::FLAG_UNDEFINED) + { + (*it)->m_flags &= ~flags; + } + } + + break; + } + default: + case message::FLAG_MODE_SET: + { + for (std::vector <IMAPMessage*>::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + if (std::binary_search(list.begin(), list.end(), (*it)->getNumber()) && + (*it)->m_flags != message::FLAG_UNDEFINED) + { + (*it)->m_flags = flags; + } + } + + break; + } + + } + + // Notify message flags changed + events::messageChangedEvent event + (thisRef().dynamicCast <folder>(), + events::messageChangedEvent::TYPE_FLAGS, nums); + + notifyMessageChanged(event); +} + + +void IMAPFolder::setMessageFlags(const string& set, const int flags, const int mode) +{ + // Build the request text + std::ostringstream command; + command << "STORE " << set; + + switch (mode) + { + case message::FLAG_MODE_ADD: command << " +FLAGS.SILENT "; break; + case message::FLAG_MODE_REMOVE: command << " -FLAGS.SILENT "; break; + default: + case message::FLAG_MODE_SET: command << " FLAGS.SILENT "; break; + } + + const string flagList = IMAPUtils::messageFlagList(flags); + + if (!flagList.empty()) + { + command << flagList; + + // Send the request + m_connection->send(true, command.str(), true); + + // Get the response + utility::auto_ptr <IMAPParser::response> resp(m_connection->readResponse()); + + if (resp->isBad() || resp->response_done()->response_tagged()-> + resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) + { + throw exceptions::command_error("STORE", + m_connection->getParser()->lastLine(), "bad response"); + } + } +} + + +void IMAPFolder::addMessage(ref <vmime::message> msg, const int flags, + vmime::datetime* date, utility::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, utility::progressionListener* progress) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + else if (m_mode == MODE_READ_ONLY) + throw exceptions::illegal_state("Folder is read-only"); + + // Build the request text + std::ostringstream command; + command << "APPEND " << IMAPUtils::quoteString(IMAPUtils::pathToString + (m_connection->hierarchySeparator(), getFullPath())) << ' '; + + const string flagList = IMAPUtils::messageFlagList(flags); + + if (flags != message::FLAG_UNDEFINED && !flagList.empty()) + { + command << flagList; + command << ' '; + } + + if (date != NULL) + { + command << IMAPUtils::dateTime(*date); + command << ' '; + } + + command << '{' << size << '}'; + + // Send the request + m_connection->send(true, command.str(), true); + + // Get the response + utility::auto_ptr <IMAPParser::response> resp(m_connection->readResponse()); + + bool ok = false; + const std::vector <IMAPParser::continue_req_or_response_data*>& respList + = resp->continue_req_or_response_data(); + + for (std::vector <IMAPParser::continue_req_or_response_data*>::const_iterator + it = respList.begin() ; !ok && (it != respList.end()) ; ++it) + { + if ((*it)->continue_req()) + ok = true; + } + + if (!ok) + { + throw exceptions::command_error("APPEND", + m_connection->getParser()->lastLine(), "bad response"); + } + + // Send message data + const int total = size; + int current = 0; + + if (progress) + progress->start(total); + + char buffer[65536]; + + while (!is.eof()) + { + // Read some data from the input stream + const int read = is.read(buffer, sizeof(buffer)); + current += read; + + // Put read data into socket output stream + m_connection->sendRaw(buffer, read); + + // Notify progression + if (progress) + progress->progress(current, total); + } + + m_connection->send(false, "", true); + + if (progress) + progress->stop(total); + + // Get the response + utility::auto_ptr <IMAPParser::response> finalResp(m_connection->readResponse()); + + if (finalResp->isBad() || finalResp->response_done()->response_tagged()-> + resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) + { + throw exceptions::command_error("APPEND", + m_connection->getParser()->lastLine(), "bad response"); + } + + // Notify message added + std::vector <int> nums; + nums.push_back(m_messageCount + 1); + + events::messageCountEvent event + (thisRef().dynamicCast <folder>(), + events::messageCountEvent::TYPE_ADDED, nums); + + m_messageCount++; + notifyMessageCount(event); + + // Notify folders with the same path + for (std::list <IMAPFolder*>::iterator it = m_store->m_folders.begin() ; + it != m_store->m_folders.end() ; ++it) + { + if ((*it) != this && (*it)->getFullPath() == m_path) + { + events::messageCountEvent event + ((*it)->thisRef().dynamicCast <folder>(), + events::messageCountEvent::TYPE_ADDED, nums); + + (*it)->m_messageCount++; + (*it)->notifyMessageCount(event); + } + } +} + + +void IMAPFolder::expunge() +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + else if (m_mode == MODE_READ_ONLY) + throw exceptions::illegal_state("Folder is read-only"); + + // Send the request + m_connection->send(true, "EXPUNGE", true); + + // Get the response + utility::auto_ptr <IMAPParser::response> resp(m_connection->readResponse()); + + if (resp->isBad() || resp->response_done()->response_tagged()-> + resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) + { + throw exceptions::command_error("EXPUNGE", + m_connection->getParser()->lastLine(), "bad response"); + } + + // Update the numbering of the messages + const std::vector <IMAPParser::continue_req_or_response_data*>& respDataList = + resp->continue_req_or_response_data(); + + std::vector <int> nums; + + for (std::vector <IMAPParser::continue_req_or_response_data*>::const_iterator + it = respDataList.begin() ; it != respDataList.end() ; ++it) + { + if ((*it)->response_data() == NULL) + { + throw exceptions::command_error("EXPUNGE", + m_connection->getParser()->lastLine(), "invalid response"); + } + + const IMAPParser::message_data* messageData = + (*it)->response_data()->message_data(); + + // We are only interested in responses of type "EXPUNGE" + if (messageData == NULL || + messageData->type() != IMAPParser::message_data::EXPUNGE) + { + continue; + } + + const int number = messageData->number(); + + nums.push_back(number); + + for (std::vector <IMAPMessage*>::iterator jt = + m_messages.begin() ; jt != m_messages.end() ; ++jt) + { + if ((*jt)->m_num == number) + (*jt)->m_expunged = true; + else if ((*jt)->m_num > number) + (*jt)->m_num--; + } + } + + m_messageCount -= nums.size(); + + // Notify message expunged + events::messageCountEvent event + (thisRef().dynamicCast <folder>(), + events::messageCountEvent::TYPE_REMOVED, nums); + + notifyMessageCount(event); + + // Notify folders with the same path + for (std::list <IMAPFolder*>::iterator it = m_store->m_folders.begin() ; + it != m_store->m_folders.end() ; ++it) + { + if ((*it) != this && (*it)->getFullPath() == m_path) + { + (*it)->m_messageCount = m_messageCount; + + events::messageCountEvent event + ((*it)->thisRef().dynamicCast <folder>(), + events::messageCountEvent::TYPE_REMOVED, nums); + + (*it)->notifyMessageCount(event); + } + } +} + + +void IMAPFolder::rename(const folder::path& newPath) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (m_path.isEmpty() || newPath.isEmpty()) + throw exceptions::illegal_operation("Cannot rename root folder"); + else if (m_path.getSize() == 1 && m_name.getBuffer() == "INBOX") + throw exceptions::illegal_operation("Cannot rename 'INBOX' folder"); + else if (!m_store->isValidFolderName(newPath.getLastComponent())) + throw exceptions::invalid_folder_name(); + + // Build the request text + std::ostringstream command; + command << "RENAME "; + command << IMAPUtils::quoteString(IMAPUtils::pathToString + (m_connection->hierarchySeparator(), getFullPath())) << " "; + command << IMAPUtils::quoteString(IMAPUtils::pathToString + (m_connection->hierarchySeparator(), newPath)); + + // Send the request + m_connection->send(true, command.str(), true); + + // Get the response + utility::auto_ptr <IMAPParser::response> resp(m_connection->readResponse()); + + if (resp->isBad() || resp->response_done()->response_tagged()-> + resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) + { + throw exceptions::command_error("RENAME", + m_connection->getParser()->lastLine(), "bad response"); + } + + // Notify folder renamed + folder::path oldPath(m_path); + + m_path = newPath; + m_name = newPath.getLastComponent(); + + events::folderEvent event + (thisRef().dynamicCast <folder>(), + events::folderEvent::TYPE_RENAMED, oldPath, newPath); + + notifyFolder(event); + + // Notify folders with the same path and sub-folders + for (std::list <IMAPFolder*>::iterator it = m_store->m_folders.begin() ; + it != m_store->m_folders.end() ; ++it) + { + if ((*it) != this && (*it)->getFullPath() == oldPath) + { + (*it)->m_path = newPath; + (*it)->m_name = newPath.getLastComponent(); + + events::folderEvent event + ((*it)->thisRef().dynamicCast <folder>(), + 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)->thisRef().dynamicCast <folder>(), + events::folderEvent::TYPE_RENAMED, oldPath, (*it)->m_path); + + (*it)->notifyFolder(event); + } + } +} + + +void IMAPFolder::copyMessage(const folder::path& dest, const int num) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + // Construct set + std::ostringstream set; + set << num; + + // Delegate message copy + copyMessages(set.str(), dest); + + // Notify message count changed + std::vector <int> nums; + nums.push_back(num); + + for (std::list <IMAPFolder*>::iterator it = m_store->m_folders.begin() ; + it != m_store->m_folders.end() ; ++it) + { + if ((*it)->getFullPath() == dest) + { + events::messageCountEvent event + ((*it)->thisRef().dynamicCast <folder>(), + events::messageCountEvent::TYPE_ADDED, nums); + + (*it)->m_messageCount++; + (*it)->notifyMessageCount(event); + } + } +} + + +void IMAPFolder::copyMessages(const folder::path& dest, const int from, const int to) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + else if (from < 1 || (to < from && to != -1)) + throw exceptions::invalid_argument(); + + // Construct set + std::ostringstream set; + + if (to == -1) + set << from << ":*"; + else + set << from << ":" << to; + + // Delegate message copy + copyMessages(set.str(), dest); + + // Notify message count changed + const int to2 = (to == -1) ? m_messageCount : to; + const int count = to - from + 1; + + std::vector <int> nums; + nums.resize(count); + + for (int i = from, j = 0 ; i <= to2 ; ++i, ++j) + nums[j] = i; + + for (std::list <IMAPFolder*>::iterator it = m_store->m_folders.begin() ; + it != m_store->m_folders.end() ; ++it) + { + if ((*it)->getFullPath() == dest) + { + events::messageCountEvent event + ((*it)->thisRef().dynamicCast <folder>(), + events::messageCountEvent::TYPE_ADDED, nums); + + (*it)->m_messageCount += count; + (*it)->notifyMessageCount(event); + } + } +} + + +void IMAPFolder::copyMessages(const folder::path& dest, const std::vector <int>& nums) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + // Delegate message copy + copyMessages(IMAPUtils::listToSet(nums, m_messageCount), dest); + + // Notify message count changed + const int count = nums.size(); + + for (std::list <IMAPFolder*>::iterator it = m_store->m_folders.begin() ; + it != m_store->m_folders.end() ; ++it) + { + if ((*it)->getFullPath() == dest) + { + events::messageCountEvent event + ((*it)->thisRef().dynamicCast <folder>(), + events::messageCountEvent::TYPE_ADDED, nums); + + (*it)->m_messageCount += count; + (*it)->notifyMessageCount(event); + } + } +} + + +void IMAPFolder::copyMessages(const string& set, const folder::path& dest) +{ + // Build the request text + std::ostringstream command; + command << "COPY " << set << " "; + command << IMAPUtils::quoteString(IMAPUtils::pathToString + (m_connection->hierarchySeparator(), dest)); + + // Send the request + m_connection->send(true, command.str(), true); + + // Get the response + utility::auto_ptr <IMAPParser::response> resp(m_connection->readResponse()); + + if (resp->isBad() || resp->response_done()->response_tagged()-> + resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) + { + throw exceptions::command_error("COPY", + m_connection->getParser()->lastLine(), "bad response"); + } +} + + +void IMAPFolder::status(int& count, int& unseen) +{ + count = 0; + unseen = 0; + + // Build the request text + std::ostringstream command; + command << "STATUS "; + command << IMAPUtils::quoteString(IMAPUtils::pathToString + (m_connection->hierarchySeparator(), getFullPath())); + command << " (MESSAGES UNSEEN)"; + + // Send the request + m_store->m_connection->send(true, command.str(), true); + + // Get the response + utility::auto_ptr <IMAPParser::response> resp(m_store->m_connection->readResponse()); + + if (resp->isBad() || resp->response_done()->response_tagged()-> + resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) + { + throw exceptions::command_error("STATUS", + m_store->m_connection->getParser()->lastLine(), "bad response"); + } + + const std::vector <IMAPParser::continue_req_or_response_data*>& respDataList = + resp->continue_req_or_response_data(); + + for (std::vector <IMAPParser::continue_req_or_response_data*>::const_iterator + it = respDataList.begin() ; it != respDataList.end() ; ++it) + { + if ((*it)->response_data() == NULL) + { + throw exceptions::command_error("STATUS", + m_store->m_connection->getParser()->lastLine(), "invalid response"); + } + + const IMAPParser::response_data* responseData = (*it)->response_data(); + + if (responseData->mailbox_data() && + responseData->mailbox_data()->type() == IMAPParser::mailbox_data::STATUS) + { + const std::vector <IMAPParser::status_info*>& statusList = + responseData->mailbox_data()->status_info_list(); + + for (std::vector <IMAPParser::status_info*>::const_iterator + jt = statusList.begin() ; jt != statusList.end() ; ++jt) + { + switch ((*jt)->status_att()->type()) + { + case IMAPParser::status_att::MESSAGES: + + count = (*jt)->number()->value(); + break; + + case IMAPParser::status_att::UNSEEN: + + unseen = (*jt)->number()->value(); + break; + + default: + + break; + } + } + } + } + + // Notify message count changed (new messages) + if (m_messageCount != count) + { + const int oldCount = m_messageCount; + + m_messageCount = count; + + if (count > oldCount) + { + std::vector <int> nums; + nums.reserve(count - oldCount); + + for (int i = oldCount + 1, j = 0 ; i <= count ; ++i, ++j) + nums[j] = i; + + events::messageCountEvent event + (thisRef().dynamicCast <folder>(), + events::messageCountEvent::TYPE_ADDED, nums); + + notifyMessageCount(event); + + // Notify folders with the same path + for (std::list <IMAPFolder*>::iterator it = m_store->m_folders.begin() ; + it != m_store->m_folders.end() ; ++it) + { + if ((*it) != this && (*it)->getFullPath() == m_path) + { + (*it)->m_messageCount = count; + + events::messageCountEvent event + ((*it)->thisRef().dynamicCast <folder>(), + events::messageCountEvent::TYPE_ADDED, nums); + + (*it)->notifyMessageCount(event); + } + } + } + } +} + + +} // imap +} // net +} // vmime diff --git a/src/net/imap/IMAPMessage.cpp b/src/net/imap/IMAPMessage.cpp new file mode 100644 index 00000000..0d9e5e00 --- /dev/null +++ b/src/net/imap/IMAPMessage.cpp @@ -0,0 +1,859 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/imap/IMAPParser.hpp" +#include "vmime/net/imap/IMAPMessage.hpp" +#include "vmime/net/imap/IMAPFolder.hpp" +#include "vmime/net/imap/IMAPStore.hpp" +#include "vmime/net/imap/IMAPConnection.hpp" +#include "vmime/net/imap/IMAPUtils.hpp" + +#include <sstream> +#include <iterator> + + +namespace vmime { +namespace net { +namespace imap { + + +// +// IMAPpart +// + +class IMAPstructure; + +class IMAPpart : public part +{ +private: + + friend class vmime::creator; + + IMAPpart(weak_ref <IMAPpart> parent, const int number, const IMAPParser::body_type_mpart* mpart); + IMAPpart(weak_ref <IMAPpart> parent, const int number, const IMAPParser::body_type_1part* part); + +public: + + const structure& getStructure() const; + structure& getStructure(); + + weak_ref <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 ref <IMAPpart> create + (weak_ref <IMAPpart> parent, const int number, const IMAPParser::body* body) + { + if (body->body_type_mpart()) + return vmime::create <IMAPpart>(parent, number, body->body_type_mpart()); + else + return vmime::create <IMAPpart>(parent, number, body->body_type_1part()); + } + + + header& getOrCreateHeader() + { + if (m_header != NULL) + return (*m_header); + else + return (*(m_header = vmime::create <header>())); + } + +private: + + ref <IMAPstructure> m_structure; + weak_ref <IMAPpart> m_parent; + ref <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(weak_ref <IMAPpart> parent, const std::vector <IMAPParser::body*>& list) + { + int number = 1; + + for (std::vector <IMAPParser::body*>::const_iterator + it = list.begin() ; it != list.end() ; ++it, ++number) + { + m_parts.push_back(IMAPpart::create(parent, number, *it)); + } + } + + + 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 <ref <IMAPpart> > m_parts; +}; + + +IMAPstructure IMAPstructure::m_emptyStructure; + + + +IMAPpart::IMAPpart(weak_ref <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 = vmime::create <IMAPstructure> + (thisWeakRef().dynamicCast <IMAPpart>(), mpart->list()); +} + + +IMAPpart::IMAPpart(weak_ref <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; +} + + +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, utility::progressionListener* progress) + : m_os(os), m_progress(progress) + { + } + + target* targetFor(const IMAPParser::component& comp, const int /* data */) + { + if (typeid(comp) == typeid(IMAPParser::msg_att_item)) + { + const int type = static_cast + <const IMAPParser::msg_att_item&>(comp).type(); + + if (type == IMAPParser::msg_att_item::BODY_SECTION || + type == IMAPParser::msg_att_item::RFC822_TEXT) + { + return new targetStream(m_progress, m_os); + } + } + + return (NULL); + } + +private: + + utility::outputStream& m_os; + utility::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_structure(NULL) +{ + m_folder->registerMessage(this); +} + + +IMAPMessage::~IMAPMessage() +{ + if (m_folder) + m_folder->unregisterMessage(this); +} + + +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); +} + + +ref <const header> IMAPMessage::getHeader() const +{ + if (m_header == NULL) + throw exceptions::unfetched_object(); + + return (m_header); +} + + +void IMAPMessage::extract(utility::outputStream& os, utility::progressionListener* progress, + const int start, const int length, const bool peek) const +{ + if (!m_folder) + throw exceptions::folder_not_found(); + + extract(NULL, os, progress, start, length, false, peek); +} + + +void IMAPMessage::extractPart + (const part& p, utility::outputStream& os, utility::progressionListener* progress, + const int start, const int length, const bool peek) const +{ + if (!m_folder) + throw exceptions::folder_not_found(); + + extract(&p, os, progress, start, length, false, peek); +} + + +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, true); + + static_cast <IMAPpart&>(p).getOrCreateHeader().parse(oss.str()); +} + + +void IMAPMessage::extract(const part* p, utility::outputStream& os, + utility::progressionListener* progress, const int start, + const int length, const bool headerOnly, const bool peek) const +{ + IMAPMessage_literalHandler literalHandler(os, progress); + + // Construct section identifier + std::ostringstream section; + + if (p != NULL) + { + weak_ref <const IMAPpart> currentPart = static_cast <const IMAPpart*>(p); + std::vector <int> numbers; + + numbers.push_back(currentPart->getNumber()); + currentPart = currentPart->getParent(); + + while (currentPart != NULL) + { + numbers.push_back(currentPart->getNumber()); + currentPart = currentPart->getParent(); + } + + numbers.erase(numbers.end() - 1); + + for (std::vector <int>::reverse_iterator it = numbers.rbegin() ; it != numbers.rend() ; ++it) + { + if (it != numbers.rbegin()) section << "."; + section << *it; + } + } + + // Build the request text + std::ostringstream command; + + command << "FETCH " << m_num << " BODY"; + if (peek) command << ".PEEK"; + command << "["; + command << section.str(); + if (headerOnly) command << ".MIME"; // "MIME" not "HEADER" for parts + command << "]"; + + if (start != 0 || length != -1) + command << "<" << start << "." << length << ">"; + + // Send the request + m_folder->m_connection->send(true, command.str(), true); + + // Get the response + utility::auto_ptr <IMAPParser::response> resp + (m_folder->m_connection->readResponse(&literalHandler)); + + if (resp->isBad() || resp->response_done()->response_tagged()-> + resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) + { + throw exceptions::command_error("FETCH", + m_folder->m_connection->getParser()->lastLine(), "bad response"); + } + + + if (!headerOnly) + { + // TODO: update the flags (eg. flag "\Seen" may have been set) + } +} + + +void IMAPMessage::fetch(IMAPFolder* folder, const int options) +{ + if (m_folder != folder) + throw exceptions::folder_not_found(); + + // TODO: optimization: send the request for multiple + // messages at the same time (FETCH x:y) + + // Example: + // C: A654 FETCH 2:4 (FLAGS BODY[HEADER.FIELDS (DATE FROM)]) + // S: * 2 FETCH .... + // S: * 3 FETCH .... + // S: * 4 FETCH .... + // S: A654 OK FETCH completed + + std::vector <string> items; + + if (options & folder::FETCH_SIZE) + items.push_back("RFC822.SIZE"); + + if (options & folder::FETCH_FLAGS) + items.push_back("FLAGS"); + + if (options & folder::FETCH_STRUCTURE) + items.push_back("BODYSTRUCTURE"); + + if (options & folder::FETCH_UID) + items.push_back("UID"); + + if (options & folder::FETCH_FULL_HEADER) + items.push_back("RFC822.HEADER"); + else + { + if (options & folder::FETCH_ENVELOPE) + items.push_back("ENVELOPE"); + + std::vector <string> headerFields; + + if (options & folder::FETCH_CONTENT_INFO) + headerFields.push_back("CONTENT_TYPE"); + + if (options & folder::FETCH_IMPORTANCE) + { + headerFields.push_back("IMPORTANCE"); + headerFields.push_back("X-PRIORITY"); + } + + if (!headerFields.empty()) + { + string list; + + for (std::vector <string>::iterator it = headerFields.begin() ; + it != headerFields.end() ; ++it) + { + if (it != headerFields.begin()) + list += " "; + + list += *it; + } + + items.push_back("BODY[HEADER.FIELDS (" + list + ")]"); + } + } + + // Build the request text + std::ostringstream command; + command << "FETCH " << m_num << " ("; + + for (std::vector <string>::const_iterator it = items.begin() ; + it != items.end() ; ++it) + { + if (it != items.begin()) command << " "; + command << *it; + } + + command << ")"; + + // Send the request + m_folder->m_connection->send(true, command.str(), true); + + // Get the response + utility::auto_ptr <IMAPParser::response> resp(m_folder->m_connection->readResponse()); + + if (resp->isBad() || resp->response_done()->response_tagged()-> + resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) + { + throw exceptions::command_error("FETCH", + m_folder->m_connection->getParser()->lastLine(), "bad response"); + } + + const std::vector <IMAPParser::continue_req_or_response_data*>& respDataList = + resp->continue_req_or_response_data(); + + for (std::vector <IMAPParser::continue_req_or_response_data*>::const_iterator + it = respDataList.begin() ; it != respDataList.end() ; ++it) + { + if ((*it)->response_data() == NULL) + { + throw exceptions::command_error("FETCH", + m_folder->m_connection->getParser()->lastLine(), "invalid response"); + } + + const IMAPParser::message_data* messageData = + (*it)->response_data()->message_data(); + + // We are only interested in responses of type "FETCH" + if (messageData == NULL || messageData->type() != IMAPParser::message_data::FETCH) + continue; + + if (static_cast <int>(messageData->number()) != m_num) + continue; + + // Process fetch response for this message + processFetchResponse(options, messageData->msg_att()); + } +} + + +void IMAPMessage::processFetchResponse + (const int options, const IMAPParser::msg_att* msgAtt) +{ + // Get message attributes + const std::vector <IMAPParser::msg_att_item*> atts = + msgAtt->items(); + + int flags = 0; + + for (std::vector <IMAPParser::msg_att_item*>::const_iterator + it = atts.begin() ; it != atts.end() ; ++it) + { + switch ((*it)->type()) + { + case IMAPParser::msg_att_item::FLAGS: + { + flags |= IMAPUtils::messageFlagsFromFlags((*it)->flag_list()); + break; + } + case IMAPParser::msg_att_item::UID: + { + std::ostringstream oss; + oss << m_folder->m_uidValidity << ":" << (*it)->unique_id()->value(); + + m_uid = oss.str(); + break; + } + case IMAPParser::msg_att_item::ENVELOPE: + { + if (!(options & folder::FETCH_FULL_HEADER)) + { + const IMAPParser::envelope* env = (*it)->envelope(); + ref <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: + { + m_structure = vmime::create <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 <ref <headerField> > fields = tempHeader.getFieldList(); + + for (std::vector <ref <headerField> >::const_iterator jt = fields.begin() ; + jt != fields.end() ; ++jt) + { + hdr.appendField((*jt)->clone().dynamicCast <headerField>()); + } + } + } + + 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; +} + + +ref <header> IMAPMessage::getOrCreateHeader() +{ + if (m_header != NULL) + return (m_header); + else + return (m_header = vmime::create <header>()); +} + + +void IMAPMessage::convertAddressList + (const IMAPParser::address_list& src, mailboxList& dest) +{ + for (std::vector <IMAPParser::address*>::const_iterator + it = src.addresses().begin() ; it != src.addresses().end() ; ++it) + { + const IMAPParser::address& addr = **it; + + text name; + text::decodeAndUnfold(addr.addr_name()->value(), &name); + + string email = addr.addr_mailbox()->value() + + "@" + addr.addr_host()->value(); + + dest.appendMailbox(vmime::create <mailbox>(name, email)); + } +} + + +void IMAPMessage::setFlags(const int flags, const int mode) +{ + if (!m_folder) + throw exceptions::folder_not_found(); + else if (m_folder->m_mode == folder::MODE_READ_ONLY) + throw exceptions::illegal_state("Folder is read-only"); + + // Build the request text + std::ostringstream command; + command << "STORE " << m_num; + + switch (mode) + { + case FLAG_MODE_ADD: command << " +FLAGS"; break; + case FLAG_MODE_REMOVE: command << " -FLAGS"; break; + default: + case FLAG_MODE_SET: command << " FLAGS"; break; + } + + if (m_flags == FLAG_UNDEFINED) // Update local flags only if they + command << ".SILENT "; // have been fetched previously + else + command << " "; + + std::vector <string> flagList; + + if (flags & FLAG_REPLIED) flagList.push_back("\\Answered"); + if (flags & FLAG_MARKED) flagList.push_back("\\Flagged"); + if (flags & FLAG_DELETED) flagList.push_back("\\Deleted"); + if (flags & FLAG_SEEN) flagList.push_back("\\Seen"); + + if (!flagList.empty()) + { + command << "("; + + if (flagList.size() >= 2) + { + std::copy(flagList.begin(), flagList.end() - 1, + std::ostream_iterator <string>(command, " ")); + } + + command << *(flagList.end() - 1) << ")"; + + // Send the request + m_folder->m_connection->send(true, command.str(), true); + + // Get the response + utility::auto_ptr <IMAPParser::response> resp(m_folder->m_connection->readResponse()); + + if (resp->isBad() || resp->response_done()->response_tagged()-> + resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) + { + throw exceptions::command_error("STORE", + m_folder->m_connection->getParser()->lastLine(), "bad response"); + } + + // Update the local flags for this message + if (m_flags != FLAG_UNDEFINED) + { + const std::vector <IMAPParser::continue_req_or_response_data*>& respDataList = + resp->continue_req_or_response_data(); + + int newFlags = 0; + + for (std::vector <IMAPParser::continue_req_or_response_data*>::const_iterator + it = respDataList.begin() ; it != respDataList.end() ; ++it) + { + if ((*it)->response_data() == NULL) + continue; + + const IMAPParser::message_data* messageData = + (*it)->response_data()->message_data(); + + // We are only interested in responses of type "FETCH" + if (messageData == NULL || messageData->type() != IMAPParser::message_data::FETCH) + continue; + + // Get message attributes + const std::vector <IMAPParser::msg_att_item*> atts = + messageData->msg_att()->items(); + + for (std::vector <IMAPParser::msg_att_item*>::const_iterator + it = atts.begin() ; it != atts.end() ; ++it) + { + if ((*it)->type() == IMAPParser::msg_att_item::FLAGS) + newFlags |= IMAPUtils::messageFlagsFromFlags((*it)->flag_list()); + } + } + + m_flags = newFlags; + } + + // Notify message flags changed + std::vector <int> nums; + nums.push_back(m_num); + + events::messageChangedEvent event + (m_folder->thisRef().dynamicCast <folder>(), + events::messageChangedEvent::TYPE_FLAGS, nums); + + for (std::list <IMAPFolder*>::iterator it = m_folder->m_store->m_folders.begin() ; + it != m_folder->m_store->m_folders.end() ; ++it) + { + if ((*it)->getFullPath() == m_folder->m_path) + (*it)->notifyMessageChanged(event); + } + } +} + + +} // imap +} // net +} // vmime diff --git a/src/net/imap/IMAPStore.cpp b/src/net/imap/IMAPStore.cpp new file mode 100644 index 00000000..c8dab178 --- /dev/null +++ b/src/net/imap/IMAPStore.cpp @@ -0,0 +1,308 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/imap/IMAPStore.hpp" +#include "vmime/net/imap/IMAPFolder.hpp" +#include "vmime/net/imap/IMAPConnection.hpp" + +#include "vmime/exception.hpp" +#include "vmime/platformDependant.hpp" + +#include <map> + + +namespace vmime { +namespace net { +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(ref <authenticator> auth) + : m_auth(auth), m_infos(NULL) + { + } + + ~IMAPauthenticator() + { + } + + const authenticationInfos requestAuthInfos() const + { + if (m_infos == NULL) + m_infos = vmime::create <authenticationInfos>(m_auth->requestAuthInfos()); + + return (*m_infos); + } + +private: + + ref <authenticator> m_auth; + mutable ref <authenticationInfos> m_infos; +}; + +#endif // VMIME_BUILDING_DOC + + + +// +// IMAPStore +// + +IMAPStore::IMAPStore(ref <session> sess, ref <authenticator> auth) + : store(sess, getInfosInstance(), auth), + m_connection(NULL), m_oneTimeAuth(NULL) +{ +} + + +IMAPStore::~IMAPStore() +{ + if (isConnected()) + disconnect(); +} + + +ref <authenticator> IMAPStore::oneTimeAuthenticator() +{ + return (m_oneTimeAuth); +} + + +const string IMAPStore::getProtocolName() const +{ + return "imap"; +} + + +ref <folder> IMAPStore::getRootFolder() +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return vmime::create <IMAPFolder>(folder::path(), this); +} + + +ref <folder> IMAPStore::getDefaultFolder() +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return vmime::create <IMAPFolder>(folder::path::component("INBOX"), this); +} + + +ref <folder> IMAPStore::getFolder(const folder::path& path) +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return vmime::create <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 = vmime::create <IMAPauthenticator>(getAuthenticator()); + + m_connection = vmime::create <IMAPConnection> + (thisWeakRef().dynamicCast <IMAPStore>(), m_oneTimeAuth); + + try + { + m_connection->connect(); + } + catch (std::exception&) + { + 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 <IMAPFolder*>::iterator it = m_folders.begin() ; + it != m_folders.end() ; ++it) + { + (*it)->onStoreDisconnected(); + } + + m_folders.clear(); + + + m_connection->disconnect(); + + m_oneTimeAuth = NULL; + + m_connection = NULL; +} + + +void IMAPStore::noop() +{ + if (!isConnected()) + throw exceptions::not_connected(); + + m_connection->send(true, "NOOP", true); + + utility::auto_ptr <IMAPParser::response> resp(m_connection->readResponse()); + + if (resp->isBad() || resp->response_done()->response_tagged()-> + resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) + { + throw exceptions::command_error("NOOP", m_connection->getParser()->lastLine()); + } +} + + +ref <IMAPConnection> IMAPStore::connection() +{ + return (m_connection); +} + + +void IMAPStore::registerFolder(IMAPFolder* folder) +{ + m_folders.push_back(folder); +} + + +void IMAPStore::unregisterFolder(IMAPFolder* folder) +{ + std::list <IMAPFolder*>::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 string IMAPStore::_infos::getPropertyPrefix() const +{ + return "store.imap."; +} + + +const IMAPStore::_infos::props& IMAPStore::_infos::getProperties() const +{ + static props p = + { + // IMAP-specific options + // (none) + + // Common properties + property(serviceInfos::property::AUTH_USERNAME, serviceInfos::property::FLAG_REQUIRED), + property(serviceInfos::property::AUTH_PASSWORD, serviceInfos::property::FLAG_REQUIRED), + + property(serviceInfos::property::SERVER_ADDRESS, serviceInfos::property::FLAG_REQUIRED), + property(serviceInfos::property::SERVER_PORT, "143"), + property(serviceInfos::property::SERVER_SOCKETFACTORY), + + property(serviceInfos::property::TIMEOUT_FACTORY) + }; + + return p; +} + + +const std::vector <serviceInfos::property> IMAPStore::_infos::getAvailableProperties() const +{ + std::vector <property> list; + const props& p = getProperties(); + + // IMAP-specific options + // (none) + + // Common properties + list.push_back(p.PROPERTY_AUTH_USERNAME); + list.push_back(p.PROPERTY_AUTH_PASSWORD); + + list.push_back(p.PROPERTY_SERVER_ADDRESS); + list.push_back(p.PROPERTY_SERVER_PORT); + list.push_back(p.PROPERTY_SERVER_SOCKETFACTORY); + + list.push_back(p.PROPERTY_TIMEOUT_FACTORY); + + return (list); +} + + + +} // imap +} // net +} // vmime diff --git a/src/net/imap/IMAPTag.cpp b/src/net/imap/IMAPTag.cpp new file mode 100644 index 00000000..5331d8bf --- /dev/null +++ b/src/net/imap/IMAPTag.cpp @@ -0,0 +1,99 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/imap/IMAPTag.hpp" + + +namespace vmime { +namespace net { +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) + : object(), 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 +} // net +} // vmime diff --git a/src/net/imap/IMAPUtils.cpp b/src/net/imap/IMAPUtils.cpp new file mode 100644 index 00000000..11111b13 --- /dev/null +++ b/src/net/imap/IMAPUtils.cpp @@ -0,0 +1,555 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/imap/IMAPUtils.hpp" +#include "vmime/net/message.hpp" + +#include <sstream> +#include <iterator> +#include <algorithm> + + +namespace vmime { +namespace net { +namespace imap { + + +const string IMAPUtils::quoteString(const string& text) +{ + // + // ATOM_CHAR ::= <any CHAR except atom_specials> + // + // atom_specials ::= "(" / ")" / "{" / SPACE / CTL / + // list_wildcards / quoted_specials + // + // list_wildcards ::= "%" / "*" + // + // quoted_specials ::= <"> / "\" + // + // CHAR ::= <any 7-bit US-ASCII character except NUL, + // 0x01 - 0x7f> + // + // CTL ::= <any ASCII control character and DEL, + // 0x00 - 0x1f, 0x7f> + // + + 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 <unsigned int>(static_cast <unsigned char>(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 <IMAPParser::mailbox_flag*>& flags = list->flags(); + + for (std::vector <IMAPParser::mailbox_flag*>::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 <IMAPParser::mailbox_flag*>& flags = list->flags(); + + for (std::vector <IMAPParser::mailbox_flag*>::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 <IMAPParser::flag*>& flagList = list->flags(); + int flags = 0; + + for (std::vector <IMAPParser::flag*>::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 <string> 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 <string>(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 <int>& list, const int max, + const bool alreadySorted) +{ + // Sort a copy of the list (if not already sorted) + std::vector <int> temp; + + if (!alreadySorted) + { + temp.resize(list.size()); + std::copy(list.begin(), list.end(), temp.begin()); + + std::sort(temp.begin(), temp.end()); + } + + const std::vector <int>& theList = (alreadySorted ? list : temp); + + // Build the set + std::ostringstream res; + int previous = -1, setBegin = -1; + + for (std::vector <int>::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 +} // net +} // vmime diff --git a/src/net/maildir/maildirFolder.cpp b/src/net/maildir/maildirFolder.cpp new file mode 100644 index 00000000..06d1ac12 --- /dev/null +++ b/src/net/maildir/maildirFolder.cpp @@ -0,0 +1,1387 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/maildir/maildirFolder.hpp" + +#include "vmime/net/maildir/maildirStore.hpp" +#include "vmime/net/maildir/maildirMessage.hpp" +#include "vmime/net/maildir/maildirUtils.hpp" + +#include "vmime/utility/smartPtr.hpp" + +#include "vmime/message.hpp" + +#include "vmime/exception.hpp" +#include "vmime/platformDependant.hpp" + + +namespace vmime { +namespace net { +namespace maildir { + + +maildirFolder::maildirFolder(const folder::path& path, weak_ref <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(); + + ref <utility::file> rootDir = fsf->create + (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_CONTAINER)); + + ref <utility::fileIterator> it = rootDir->getFiles(); + + while (it->hasMoreElements()) + { + ref <utility::file> 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 <maildirMessage*>::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 <maildirMessage*>::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(); + + ref <utility::file> rootDir = fsf->create + (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_ROOT)); + + ref <utility::file> newDir = fsf->create + (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_NEW)); + ref <utility::file> tmpDir = fsf->create + (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_TMP)); + ref <utility::file> 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 + (thisRef().dynamicCast <folder>(), + events::folderEvent::TYPE_CREATED, m_path, m_path); + + notifyFolder(event); +} + + +const bool maildirFolder::exists() +{ + utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); + + ref <utility::file> rootDir = fsf->create + (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_ROOT)); + + ref <utility::file> newDir = fsf->create + (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_NEW)); + ref <utility::file> tmpDir = fsf->create + (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_TMP)); + ref <utility::file> 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); + ref <utility::file> newDir = fsf->create(newDirPath); + + utility::file::path curDirPath = maildirUtils::getFolderFSPath + (m_store, m_path, maildirUtils::FOLDER_PATH_CUR); + ref <utility::file> curDir = fsf->create(curDirPath); + + // New received messages (new/) + ref <utility::fileIterator> nit = newDir->getFiles(); + std::vector <utility::file::path::component> newMessageFilenames; + + while (nit->hasMoreElements()) + { + ref <utility::file> file = nit->nextElement(); + + if (maildirUtils::isMessageFile(*file)) + newMessageFilenames.push_back(file->getFullPath().getLastComponent()); + } + + // Current messages (cur/) + ref <utility::fileIterator> cit = curDir->getFiles(); + std::vector <utility::file::path::component> curMessageFilenames; + + while (cit->hasMoreElements()) + { + ref <utility::file> file = cit->nextElement(); + + if (maildirUtils::isMessageFile(*file)) + curMessageFilenames.push_back(file->getFullPath().getLastComponent()); + } + + // 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 <utility::file::path::component>::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 <utility::file::path::component>::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' + ref <utility::file> 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 <utility::file::path::component>::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 <messageInfos>::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... + } +} + + +ref <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 vmime::create <maildirMessage> + (thisWeakRef().dynamicCast <maildirFolder>(), num); +} + + +std::vector <ref <message> > maildirFolder::getMessages(const int from, const int to) +{ + const int to2 = (to == -1 ? m_messageCount : to); + + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + else if (to2 < from || from < 1 || to2 < 1 || from > m_messageCount || to2 > m_messageCount) + throw exceptions::message_not_found(); + + std::vector <ref <message> > v; + + for (int i = from ; i <= to2 ; ++i) + { + v.push_back(vmime::create <maildirMessage> + (thisWeakRef().dynamicCast <maildirFolder>(), i)); + } + + return (v); +} + + +std::vector <ref <message> > maildirFolder::getMessages(const std::vector <int>& nums) +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + std::vector <ref <message> > v; + + for (std::vector <int>::const_iterator it = nums.begin() ; it != nums.end() ; ++it) + { + v.push_back(vmime::create <maildirMessage> + (thisWeakRef().dynamicCast <maildirFolder>(), *it)); + } + + return (v); +} + + +const int maildirFolder::getMessageCount() +{ + return (m_messageCount); +} + + +ref <folder> maildirFolder::getFolder(const folder::path::component& name) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + + return vmime::create <maildirFolder>(m_path / name, m_store); +} + + +std::vector <ref <folder> > maildirFolder::getFolders(const bool recursive) +{ + if (!isOpen() && !m_store) + throw exceptions::illegal_state("Store disconnected"); + + std::vector <ref <folder> > list; + + listFolders(list, recursive); + + return (list); +} + + +void maildirFolder::listFolders(std::vector <ref <folder> >& list, const bool recursive) +{ + try + { + utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); + + ref <utility::file> rootDir = fsf->create + (maildirUtils::getFolderFSPath(m_store, m_path, + m_path.isEmpty() ? maildirUtils::FOLDER_PATH_ROOT + : maildirUtils::FOLDER_PATH_CONTAINER)); + + if (rootDir->exists()) + { + ref <utility::fileIterator> it = rootDir->getFiles(); + + while (it->hasMoreElements()) + { + ref <utility::file> file = it->nextElement(); + + if (maildirUtils::isSubfolderDirectory(*file)) + { + const utility::path subPath = + m_path / file->getFullPath().getLastComponent(); + + ref <maildirFolder> subFolder = + vmime::create <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(); + + ref <utility::file> rootDir = fsf->create + (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_ROOT)); + ref <utility::file> 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 + (thisRef().dynamicCast <folder>(), + events::folderEvent::TYPE_RENAMED, oldPath, newPath); + + notifyFolder(event); + + // Notify folders with the same path + for (std::list <maildirFolder*>::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)->thisRef().dynamicCast <folder>(), + 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)->thisRef().dynamicCast <folder>(), + 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 <int>& 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 <int> 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 <maildirMessage*>::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 <maildirMessage*>::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 <maildirMessage*>::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 + (thisRef().dynamicCast <folder>(), + events::messageChangedEvent::TYPE_FLAGS, nums); + + notifyMessageChanged(event); + + // TODO: notify other folders with the same path +} + + +void maildirFolder::setMessageFlags + (const std::vector <int>& nums, const int flags, const int mode) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + else if (m_mode == MODE_READ_ONLY) + throw exceptions::illegal_state("Folder is read-only"); + + // Sort the list of message numbers + std::vector <int> list; + + list.resize(nums.size()); + std::copy(nums.begin(), nums.end(), list.begin()); + + std::sort(list.begin(), list.end()); + + // Change message flags + setMessageFlagsImpl(list, flags, mode); + + // Update local flags + switch (mode) + { + case message::FLAG_MODE_ADD: + { + for (std::vector <maildirMessage*>::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 <maildirMessage*>::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 <maildirMessage*>::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 + (thisRef().dynamicCast <folder>(), + events::messageChangedEvent::TYPE_FLAGS, nums); + + notifyMessageChanged(event); + + // TODO: notify other folders with the same path +} + + +void maildirFolder::setMessageFlagsImpl + (const std::vector <int>& 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 <int>::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; + ref <utility::file> 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(ref <vmime::message> msg, const int flags, + vmime::datetime* date, utility::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 */, utility::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 + { + ref <utility::file> tmpDir = fsf->create(tmpDirPath); + tmpDir->createDirectory(true); + } + catch (exceptions::filesystem_exception&) + { + // Don't throw now, it will fail later... + } + + try + { + ref <utility::file> 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 <int> nums; + nums.push_back(m_messageCount); + + events::messageCountEvent event + (thisRef().dynamicCast <folder>(), + events::messageCountEvent::TYPE_ADDED, nums); + + notifyMessageCount(event); + + // Notify folders with the same path + for (std::list <maildirFolder*>::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)->thisRef().dynamicCast <folder>(), + 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, + utility::progressionListener* progress) +{ + utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); + + ref <utility::file> file = fsf->create(tmpDirPath / filename); + + if (progress) + progress->start(size); + + // First, write the message into 'tmp'... + try + { + file->createFile(); + + ref <utility::fileWriter> fw = file->getFileWriter(); + ref <utility::outputStream> 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 + { + ref <utility::file> 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 + { + ref <utility::file> 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 <int> 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 <int>& 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 <int>& 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 + { + ref <utility::file> destTmpDir = fsf->create(destTmpDirPath); + destTmpDir->createDirectory(true); + } + catch (exceptions::filesystem_exception&) + { + // Don't throw now, it will fail later... + } + + try + { + ref <utility::file> 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 <int>::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); + + ref <utility::file> file = fsf->create(curDirPath / msg.path); + ref <utility::fileReader> fr = file->getFileReader(); + ref <utility::inputStream> 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 <maildirFolder*>::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 <int> nums; + nums.reserve(count - oldCount); + + for (int i = oldCount + 1, j = 0 ; i <= count ; ++i, ++j) + nums[j] = i; + + events::messageCountEvent event + (thisRef().dynamicCast <folder>(), + events::messageCountEvent::TYPE_ADDED, nums); + + notifyMessageCount(event); + + // Notify folders with the same path + for (std::list <maildirFolder*>::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)->thisRef().dynamicCast <folder>(), + 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 <int> 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 <maildirMessage*>::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 + { + ref <utility::file> 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 + (thisRef().dynamicCast <folder>(), + events::messageCountEvent::TYPE_REMOVED, nums); + + notifyMessageCount(event); + + // Notify folders with the same path + for (std::list <maildirFolder*>::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)->thisRef().dynamicCast <folder>(), + events::messageCountEvent::TYPE_REMOVED, nums); + + (*it)->notifyMessageCount(event); + } + } +} + + +ref <folder> maildirFolder::getParent() +{ + if (m_path.isEmpty()) + return NULL; + else + return vmime::create <maildirFolder>(m_path.getParent(), m_store); +} + + +weak_ref <const store> maildirFolder::getStore() const +{ + return (m_store); +} + + +weak_ref <store> maildirFolder::getStore() +{ + return (m_store); +} + + +void maildirFolder::fetchMessages(std::vector <ref <message> >& msg, + const int options, utility::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); + + weak_ref <maildirFolder> _this = thisWeakRef().dynamicCast <maildirFolder>(); + + for (std::vector <ref <message> >::iterator it = msg.begin() ; + it != msg.end() ; ++it) + { + (*it).dynamicCast <maildirMessage>()->fetch(_this, options); + + if (progress) + progress->progress(++current, total); + } + + if (progress) + progress->stop(total); +} + + +void maildirFolder::fetchMessage(ref <message> msg, const int options) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + msg.dynamicCast <maildirMessage>()->fetch + (thisWeakRef().dynamicCast <maildirFolder>(), options); +} + + +const int maildirFolder::getFetchCapabilities() const +{ + return (FETCH_ENVELOPE | FETCH_STRUCTURE | FETCH_CONTENT_INFO | + FETCH_FLAGS | FETCH_SIZE | FETCH_FULL_HEADER | FETCH_UID | + FETCH_IMPORTANCE); +} + + +const utility::file::path maildirFolder::getMessageFSPath(const int number) const +{ + utility::file::path curDirPath = maildirUtils::getFolderFSPath + (m_store, m_path, maildirUtils::FOLDER_PATH_CUR); + + return (curDirPath / m_messageInfos[number - 1].path); +} + + +} // maildir +} // net +} // vmime diff --git a/src/net/maildir/maildirMessage.cpp b/src/net/maildir/maildirMessage.cpp new file mode 100644 index 00000000..358b6046 --- /dev/null +++ b/src/net/maildir/maildirMessage.cpp @@ -0,0 +1,505 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/maildir/maildirMessage.hpp" +#include "vmime/net/maildir/maildirFolder.hpp" +#include "vmime/net/maildir/maildirUtils.hpp" +#include "vmime/net/maildir/maildirStore.hpp" + +#include "vmime/message.hpp" + +#include "vmime/exception.hpp" +#include "vmime/platformDependant.hpp" + + +namespace vmime { +namespace net { +namespace maildir { + + +// +// maildirPart +// + +class maildirStructure; + +class maildirPart : public part +{ +public: + + maildirPart(weak_ref <maildirPart> parent, const int number, const bodyPart& part); + ~maildirPart(); + + + const structure& getStructure() const; + structure& getStructure(); + + weak_ref <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 = vmime::create <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: + + ref <maildirStructure> m_structure; + weak_ref <maildirPart> m_parent; + ref <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(weak_ref <maildirPart> parent, const bodyPart& part) + { + m_parts.push_back(vmime::create <maildirPart>(parent, 1, part)); + } + + maildirStructure(weak_ref <maildirPart> parent, const std::vector <ref <const vmime::bodyPart> >& list) + { + int number = 1; + + for (unsigned int i = 0 ; i < list.size() ; ++i) + m_parts.push_back(vmime::create <maildirPart>(parent, number, *list[i])); + } + + + 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 <ref <maildirPart> > m_parts; +}; + + +maildirStructure maildirStructure::m_emptyStructure; + + + +maildirPart::maildirPart(weak_ref <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 = vmime::create <maildirStructure> + (thisWeakRef().dynamicCast <maildirPart>(), + 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() +{ +} + + +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(weak_ref <maildirFolder> folder, const int num) + : m_folder(folder), m_num(num), m_size(-1), m_flags(FLAG_UNDEFINED), + m_expunged(false), m_structure(NULL) +{ + m_folder->registerMessage(this); +} + + +maildirMessage::~maildirMessage() +{ + if (m_folder) + m_folder->unregisterMessage(this); +} + + +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); +} + + +ref <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, + utility::progressionListener* progress, const int start, + const int length, const bool peek) const +{ + extractImpl(os, progress, 0, m_size, start, length, peek); +} + + +void maildirMessage::extractPart(const part& p, utility::outputStream& os, + utility::progressionListener* progress, const int start, + const int length, const bool peek) const +{ + const maildirPart& mp = dynamic_cast <const maildirPart&>(p); + + extractImpl(os, progress, mp.getBodyParsedOffset(), mp.getBodyParsedLength(), + start, length, peek); +} + + +void maildirMessage::extractImpl(utility::outputStream& os, utility::progressionListener* progress, + const int start, const int length, const int partialStart, const int partialLength, + const bool /* peek */) const +{ + utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); + + const utility::file::path path = m_folder->getMessageFSPath(m_num); + ref <utility::file> file = fsf->create(path); + + ref <utility::fileReader> reader = file->getFileReader(); + ref <utility::inputStream> 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); + + // TODO: mark as read unless 'peek' is set +} + + +void maildirMessage::fetchPartHeader(part& p) +{ + maildirPart& mp = dynamic_cast <maildirPart&>(p); + + utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); + + const utility::file::path path = m_folder->getMessageFSPath(m_num); + ref <utility::file> file = fsf->create(path); + + ref <utility::fileReader> reader = file->getFileReader(); + ref <utility::inputStream> 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(weak_ref <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); + ref <utility::file> 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 | + folder::FETCH_IMPORTANCE)) + { + string contents; + + ref <utility::fileReader> reader = file->getFileReader(); + ref <utility::inputStream> 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) + { + m_structure = vmime::create <maildirStructure>(null, msg); + } + + // Extract some header fields or whole header + if (options & (folder::FETCH_ENVELOPE | + folder::FETCH_CONTENT_INFO | + folder::FETCH_FULL_HEADER | + folder::FETCH_IMPORTANCE)) + { + getOrCreateHeader()->copyFrom(*(msg.getHeader())); + } + } +} + + +ref <header> maildirMessage::getOrCreateHeader() +{ + if (m_header != NULL) + return (m_header); + else + return (m_header = vmime::create <header>()); +} + + +} // maildir +} // net +} // vmime diff --git a/src/net/maildir/maildirStore.cpp b/src/net/maildir/maildirStore.cpp new file mode 100644 index 00000000..731024fa --- /dev/null +++ b/src/net/maildir/maildirStore.cpp @@ -0,0 +1,250 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/maildir/maildirStore.hpp" + +#include "vmime/net/maildir/maildirFolder.hpp" + +#include "vmime/utility/smartPtr.hpp" + +#include "vmime/exception.hpp" +#include "vmime/platformDependant.hpp" + + +// Helpers for service properties +#define GET_PROPERTY(type, prop) \ + (sm_infos.getPropertyValue <type>(getSession(), sm_infos.getProperties().prop)) +#define HAS_PROPERTY(prop) \ + (sm_infos.hasProperty(getSession(), sm_infos.getProperties().prop)) + + +namespace vmime { +namespace net { +namespace maildir { + + +maildirStore::maildirStore(ref <session> sess, ref <authenticator> auth) + : store(sess, getInfosInstance(), auth), m_connected(false) +{ +} + + +maildirStore::~maildirStore() +{ + if (isConnected()) + disconnect(); +} + + +const string maildirStore::getProtocolName() const +{ + return "maildir"; +} + + +ref <folder> maildirStore::getRootFolder() +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return vmime::create <maildirFolder>(folder::path(), + thisWeakRef().dynamicCast <maildirStore>()); +} + + +ref <folder> maildirStore::getDefaultFolder() +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return vmime::create <maildirFolder>(folder::path::component("inbox"), + thisWeakRef().dynamicCast <maildirStore>()); +} + + +ref <folder> maildirStore::getFolder(const folder::path& path) +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return vmime::create <maildirFolder>(path, + thisWeakRef().dynamicCast <maildirStore>()); +} + + +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(GET_PROPERTY(string, PROPERTY_SERVER_ROOTPATH)); + + ref <utility::file> 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 <maildirFolder*>::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 <maildirFolder*>::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 string maildirStore::_infos::getPropertyPrefix() const +{ + return "store.maildir."; +} + + +const maildirStore::_infos::props& maildirStore::_infos::getProperties() const +{ + static props p = + { + property(serviceInfos::property::SERVER_ROOTPATH, serviceInfos::property::FLAG_REQUIRED) + }; + + return p; +} + + +const std::vector <serviceInfos::property> maildirStore::_infos::getAvailableProperties() const +{ + std::vector <property> list; + const props& p = getProperties(); + + // Maildir-specific properties + list.push_back(p.PROPERTY_SERVER_ROOTPATH); + + return (list); +} + + +} // maildir +} // net +} // vmime diff --git a/src/net/maildir/maildirUtils.cpp b/src/net/maildir/maildirUtils.cpp new file mode 100644 index 00000000..b0f43533 --- /dev/null +++ b/src/net/maildir/maildirUtils.cpp @@ -0,0 +1,208 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/maildir/maildirUtils.hpp" +#include "vmime/net/maildir/maildirStore.hpp" + +#include "vmime/utility/random.hpp" + + +namespace vmime { +namespace net { +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 + (weak_ref <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 bool maildirUtils::isMessageFile(const utility::file& file) +{ + // Ignore files which name begins with '.' + if (file.isFile() && + 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 +} // net +} // vmime diff --git a/src/net/message.cpp b/src/net/message.cpp new file mode 100644 index 00000000..0c55d1da --- /dev/null +++ b/src/net/message.cpp @@ -0,0 +1,46 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/message.hpp" + + +namespace vmime { +namespace net { + + +const part& part::operator[](const int x) const +{ + return (getStructure()[x]); +} + + +part& part::operator[](const int x) +{ + return (getStructure()[x]); +} + + +const int part::getCount() const +{ + return (getStructure().getCount()); +} + + +} // net +} // vmime diff --git a/src/net/pop3/POP3Folder.cpp b/src/net/pop3/POP3Folder.cpp new file mode 100644 index 00000000..807f9b6c --- /dev/null +++ b/src/net/pop3/POP3Folder.cpp @@ -0,0 +1,826 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/pop3/POP3Folder.hpp" + +#include "vmime/net/pop3/POP3Store.hpp" +#include "vmime/net/pop3/POP3Message.hpp" + +#include "vmime/exception.hpp" + + +namespace vmime { +namespace net { +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); +} + + +ref <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 vmime::create <POP3Message>(this, num); +} + + +std::vector <ref <message> > POP3Folder::getMessages(const int from, const int to) +{ + const int to2 = (to == -1 ? m_messageCount : to); + + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + else if (to2 < from || from < 1 || to2 < 1 || from > m_messageCount || to2 > m_messageCount) + throw exceptions::message_not_found(); + + std::vector <ref <message> > v; + + for (int i = from ; i <= to2 ; ++i) + v.push_back(vmime::create <POP3Message>(this, i)); + + return (v); +} + + +std::vector <ref <message> > POP3Folder::getMessages(const std::vector <int>& nums) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + std::vector <ref <message> > v; + + for (std::vector <int>::const_iterator it = nums.begin() ; it != nums.end() ; ++it) + { + if (*it < 1|| *it > m_messageCount) + throw exceptions::message_not_found(); + + v.push_back(vmime::create <POP3Message>(this, *it)); + } + + 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); +} + + +ref <folder> POP3Folder::getFolder(const folder::path::component& name) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + + return vmime::create <POP3Folder>(m_path / name, m_store); +} + + +std::vector <ref <folder> > POP3Folder::getFolders(const bool /* recursive */) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + + if (m_path.isEmpty()) + { + std::vector <ref <folder> > v; + v.push_back(vmime::create <POP3Folder>(folder::path::component("INBOX"), m_store)); + return (v); + } + else + { + std::vector <ref <folder> > v; + return (v); + } +} + + +void POP3Folder::fetchMessages(std::vector <ref <message> >& msg, const int options, + utility::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 <ref <message> >::iterator it = msg.begin() ; + it != msg.end() ; ++it) + { + (*it).dynamicCast <POP3Message>()->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 <int, string> result; + parseMultiListOrUidlResponse(response, result); + + for (std::vector <ref <message> >::iterator it = msg.begin() ; + it != msg.end() ; ++it) + { + ref <POP3Message> m = (*it).dynamicCast <POP3Message>(); + + std::map <int, string>::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 <int, string> result; + parseMultiListOrUidlResponse(response, result); + + for (std::vector <ref <message> >::iterator it = msg.begin() ; + it != msg.end() ; ++it) + { + ref <POP3Message> m = (*it).dynamicCast <POP3Message>(); + + std::map <int, string>::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(ref <message> msg, const int options) +{ + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + msg.dynamicCast <POP3Message>()->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; + + msg.dynamicCast <POP3Message>()->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()) + { + msg.dynamicCast <POP3Message>()->m_uid = + string(it, response.end()); + } + } + } +} + + +const int POP3Folder::getFetchCapabilities() const +{ + return (FETCH_ENVELOPE | FETCH_CONTENT_INFO | + FETCH_SIZE | FETCH_FULL_HEADER | FETCH_UID | + FETCH_IMPORTANCE); +} + + +ref <folder> POP3Folder::getParent() +{ + if (m_path.isEmpty()) + return NULL; + else + return vmime::create <POP3Folder>(m_path.getParent(), m_store); +} + + +weak_ref <const store> POP3Folder::getStore() const +{ + return (m_store); +} + + +weak_ref <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 <POP3Message*, int>::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 <int> nums; + nums.push_back(num); + + events::messageChangedEvent event + (thisRef().dynamicCast <folder>(), + 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 <POP3Message*, int>::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 <int> nums; + + for (int i = from ; i <= to2 ; ++i) + nums.push_back(i); + + events::messageChangedEvent event + (thisRef().dynamicCast <folder>(), + events::messageChangedEvent::TYPE_FLAGS, nums); + + notifyMessageChanged(event); +} + + +void POP3Folder::deleteMessages(const std::vector <int>& nums) +{ + if (nums.empty()) + throw exceptions::invalid_argument(); + + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + for (std::vector <int>::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 <int> 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 <POP3Message*, int>::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 + (thisRef().dynamicCast <folder>(), + 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 <int>& /* 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(ref <vmime::message> /* msg */, const int /* flags */, + vmime::datetime* /* date */, utility::progressionListener* /* progress */) +{ + throw exceptions::operation_not_supported(); +} + + +void POP3Folder::addMessage(utility::inputStream& /* is */, const int /* size */, const int /* flags */, + vmime::datetime* /* date */, utility::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 <int>& /* 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 <int> 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 + (thisRef().dynamicCast <folder>(), + events::messageCountEvent::TYPE_ADDED, nums); + + notifyMessageCount(event); + + // Notify folders with the same path + for (std::list <POP3Folder*>::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)->thisRef().dynamicCast <folder>(), + 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 <int, string>& result) +{ + std::istringstream iss(response); + std::map <int, string> 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 <int, string>::value_type(number, string(it, line.end()))); + } + } + } +} + + +} // pop3 +} // net +} // vmime diff --git a/src/net/pop3/POP3Message.cpp b/src/net/pop3/POP3Message.cpp new file mode 100644 index 00000000..b2e2fae4 --- /dev/null +++ b/src/net/pop3/POP3Message.cpp @@ -0,0 +1,213 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/pop3/POP3Message.hpp" +#include "vmime/net/pop3/POP3Folder.hpp" +#include "vmime/net/pop3/POP3Store.hpp" + +#include <sstream> + + +namespace vmime { +namespace net { +namespace pop3 { + + +POP3Message::POP3Message(POP3Folder* folder, const int num) + : m_folder(folder), m_num(num), m_size(-1), m_deleted(false) +{ + m_folder->registerMessage(this); +} + + +POP3Message::~POP3Message() +{ + if (m_folder) + m_folder->unregisterMessage(this); +} + + +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(); +} + + +ref <const header> POP3Message::getHeader() const +{ + if (m_header == NULL) + throw exceptions::unfetched_object(); + + return (m_header); +} + + +void POP3Message::extract(utility::outputStream& os, + utility::progressionListener* progress, const int start, + const int length, const bool /* peek */) 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 <POP3Folder*>(m_folder)->m_store->sendRequest(oss.str()); + + try + { + POP3Folder::MessageMap::const_iterator it = + m_folder->m_messages.find(const_cast <POP3Message*>(this)); + + const int totalSize = (it != m_folder->m_messages.end()) + ? (*it).second : 0; + + const_cast <POP3Folder*>(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 */, + utility::progressionListener* /* progress */, + const int /* start */, const int /* length */, + const bool /* peek */) 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 + static const int optionsRequiringHeader = + folder::FETCH_ENVELOPE | folder::FETCH_CONTENT_INFO | + folder::FETCH_FULL_HEADER | folder::FETCH_IMPORTANCE; + + if (!(options & optionsRequiringHeader)) + 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); + + m_header = vmime::create <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 +} // net +} // vmime diff --git a/src/net/pop3/POP3Store.cpp b/src/net/pop3/POP3Store.cpp new file mode 100644 index 00000000..9ccfa7b7 --- /dev/null +++ b/src/net/pop3/POP3Store.cpp @@ -0,0 +1,630 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/pop3/POP3Store.hpp" +#include "vmime/net/pop3/POP3Folder.hpp" + +#include "vmime/exception.hpp" +#include "vmime/platformDependant.hpp" +#include "vmime/messageId.hpp" +#include "vmime/utility/md5.hpp" +#include "vmime/utility/filteredStream.hpp" + +#include <algorithm> + + +// Helpers for service properties +#define GET_PROPERTY(type, prop) \ + (sm_infos.getPropertyValue <type>(getSession(), sm_infos.getProperties().prop)) +#define HAS_PROPERTY(prop) \ + (sm_infos.hasProperty(getSession(), sm_infos.getProperties().prop)) + + +namespace vmime { +namespace net { +namespace pop3 { + + +POP3Store::POP3Store(ref <session> sess, ref <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"; +} + + +ref <folder> POP3Store::getDefaultFolder() +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return vmime::create <POP3Folder>(folder::path(folder::path::component("INBOX")), this); +} + + +ref <folder> POP3Store::getRootFolder() +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return vmime::create <POP3Folder>(folder::path(), this); +} + + +ref <folder> POP3Store::getFolder(const folder::path& path) +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return vmime::create <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 = GET_PROPERTY(string, PROPERTY_SERVER_ADDRESS); + const port_t port = GET_PROPERTY(port_t, PROPERTY_SERVER_PORT); + + // Create the time-out handler + if (HAS_PROPERTY(PROPERTY_TIMEOUT_FACTORY)) + { + timeoutHandlerFactory* tof = platformDependant::getHandler()-> + getTimeoutHandlerFactory(GET_PROPERTY(string, PROPERTY_TIMEOUT_FACTORY)); + + m_timeoutHandler = tof->create(); + } + + // Create and connect the socket + socketFactory* sf = platformDependant::getHandler()-> + getSocketFactory(GET_PROPERTY(string, PROPERTY_SERVER_SOCKETFACTORY)); + + m_socket = sf->create(); + m_socket->connect(address, port); + + // Connection + // + // eg: C: <connection to server> + // --- S: +OK MailSite POP3 Server 5.3.4.0 Ready <[email protected]> + + 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 <digest> + // --- S: +OK vincent is a valid mailbox + messageId mid(response); + + if (GET_PROPERTY(bool, PROPERTY_OPTIONS_APOP)) + { + if (mid.getLeft().length() && mid.getRight().length()) + { + // <digest> is the result of MD5 applied to "<message-id>password" + sendRequest("APOP " + auth.getUsername() + " " + + utility::md5(mid.generate() + auth.getPassword()).hex()); + readResponse(response, false); + + if (isSuccessResponse(response)) + { + authentified = true; + } + else + { + if (!GET_PROPERTY(bool, PROPERTY_OPTIONS_APOP_FALLBACK)) + { + internalDisconnect(); + throw exceptions::authentication_error(response); + } + } + } + else + { + // APOP not supported + if (!GET_PROPERTY(bool, PROPERTY_OPTIONS_APOP_FALLBACK)) + { + // 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 <POP3Folder*>::iterator it = m_folders.begin() ; + it != m_folders.end() ; ++it) + { + (*it)->onStoreDisconnected(); + } + + m_folders.clear(); + + + sendRequest("QUIT"); + + m_socket->disconnect(); + m_socket = NULL; + + 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, + utility::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, + utility::progressionListener* progress, const int predictedSize) +{ + int current = 0, total = predictedSize; + + string temp; + bool codeDone = false; + + if (progress) + progress->start(total); + + if (m_timeoutHandler) + m_timeoutHandler->resetTimeOut(); + + utility::inputStreamSocketAdapter sis(*m_socket); + utility::stopSequenceFilteredInputStream <5> sfis1(sis, "\r\n.\r\n"); + utility::stopSequenceFilteredInputStream <3> sfis2(sfis1, "\n.\n"); + utility::dotFilteredInputStream dfis(sfis2); // "\n.." --> "\n." + + utility::inputStream& is = dfis; + + while (!is.eof()) + { +#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 + utility::stream::value_type buffer[65536]; + const utility::stream::size_type read = is.read(buffer, sizeof(buffer)); + + if (read == 0) // buffer is empty + { + platformDependant::getHandler()->wait(); + continue; + } + + // We have received data: reset the time-out counter + if (m_timeoutHandler) + m_timeoutHandler->resetTimeOut(); + + // If we don't have extracted the response code yet + if (!codeDone) + { + temp += string(buffer, read); + + string firstLine; + + if (stripFirstLine(temp, temp, &firstLine) == true) + { + if (!isSuccessResponse(firstLine)) + throw exceptions::command_error("?", firstLine); + + codeDone = true; + + os.write(temp.data(), temp.length()); + temp.clear(); + + continue; + } + } + else + { + // Inject the data into the output stream + os.write(buffer, read); + current += read; + + // 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 <POP3Folder*>::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 string POP3Store::_infos::getPropertyPrefix() const +{ + return "store.pop3."; +} + + +const POP3Store::_infos::props& POP3Store::_infos::getProperties() const +{ + static props p = + { + // POP3-specific options + property("options.apop", serviceInfos::property::TYPE_BOOL, "false"), + property("options.apop.fallback", serviceInfos::property::TYPE_BOOL, "false"), + + // Common properties + property(serviceInfos::property::AUTH_USERNAME, serviceInfos::property::FLAG_REQUIRED), + property(serviceInfos::property::AUTH_PASSWORD, serviceInfos::property::FLAG_REQUIRED), + + property(serviceInfos::property::SERVER_ADDRESS, serviceInfos::property::FLAG_REQUIRED), + property(serviceInfos::property::SERVER_PORT, "110"), + property(serviceInfos::property::SERVER_SOCKETFACTORY), + + property(serviceInfos::property::TIMEOUT_FACTORY) + }; + + return p; +} + + +const std::vector <serviceInfos::property> POP3Store::_infos::getAvailableProperties() const +{ + std::vector <property> list; + const props& p = getProperties(); + + // POP3-specific options + list.push_back(p.PROPERTY_OPTIONS_APOP); + list.push_back(p.PROPERTY_OPTIONS_APOP_FALLBACK); + + // Common properties + list.push_back(p.PROPERTY_AUTH_USERNAME); + list.push_back(p.PROPERTY_AUTH_PASSWORD); + + list.push_back(p.PROPERTY_SERVER_ADDRESS); + list.push_back(p.PROPERTY_SERVER_PORT); + list.push_back(p.PROPERTY_SERVER_SOCKETFACTORY); + + list.push_back(p.PROPERTY_TIMEOUT_FACTORY); + + return (list); +} + + +} // pop3 +} // net +} // vmime + diff --git a/src/net/sendmail/sendmailTransport.cpp b/src/net/sendmail/sendmailTransport.cpp new file mode 100644 index 00000000..1b6edff9 --- /dev/null +++ b/src/net/sendmail/sendmailTransport.cpp @@ -0,0 +1,222 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/sendmail/sendmailTransport.hpp" + +#include "vmime/exception.hpp" +#include "vmime/platformDependant.hpp" +#include "vmime/message.hpp" +#include "vmime/mailboxList.hpp" + +#include "vmime/utility/filteredStream.hpp" +#include "vmime/utility/childProcess.hpp" +#include "vmime/utility/smartPtr.hpp" + +#include "vmime/config.hpp" + + +// Helpers for service properties +#define GET_PROPERTY(type, prop) \ + (sm_infos.getPropertyValue <type>(getSession(), sm_infos.getProperties().prop)) +#define HAS_PROPERTY(prop) \ + (sm_infos.hasProperty(getSession(), sm_infos.getProperties().prop)) + + +#if VMIME_BUILTIN_PLATFORM_POSIX + + +namespace vmime { +namespace net { +namespace sendmail { + + +sendmailTransport::sendmailTransport(ref <session> sess, ref <authenticator> auth) + : transport(sess, getInfosInstance(), auth), m_connected(false) +{ +} + + +sendmailTransport::~sendmailTransport() +{ + if (isConnected()) + disconnect(); +} + + +const string sendmailTransport::getProtocolName() const +{ + return "sendmail"; +} + + +void sendmailTransport::connect() +{ + if (isConnected()) + throw exceptions::already_connected(); + + // Use the specified path for 'sendmail' or a default one if no path is specified + m_sendmailPath = GET_PROPERTY(string, PROPERTY_BINPATH); + + m_connected = true; +} + + +const bool sendmailTransport::isConnected() const +{ + return (m_connected); +} + + +void sendmailTransport::disconnect() +{ + if (!isConnected()) + throw exceptions::not_connected(); + + internalDisconnect(); +} + + +void sendmailTransport::internalDisconnect() +{ + m_connected = false; +} + + +void sendmailTransport::noop() +{ + // Do nothing +} + + +void sendmailTransport::send + (const mailbox& expeditor, const mailboxList& recipients, + utility::inputStream& is, const utility::stream::size_type size, + utility::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(); + + // Construct the argument list + std::vector <string> args; + + args.push_back("-i"); + args.push_back("-f"); + args.push_back(expeditor.getEmail()); + args.push_back("--"); + + for (int i = 0 ; i < recipients.getMailboxCount() ; ++i) + args.push_back(recipients.getMailboxAt(i)->getEmail()); + + // Call sendmail + try + { + internalSend(args, is, size, progress); + } + catch (vmime::exception& e) + { + throw exceptions::command_error("SEND", "", "sendmail failed", e); + } +} + + +void sendmailTransport::internalSend + (const std::vector <string> args, utility::inputStream& is, + const utility::stream::size_type size, utility::progressionListener* progress) +{ + const utility::file::path path = vmime::platformDependant::getHandler()-> + getFileSystemFactory()->stringToPath(m_sendmailPath); + + ref <utility::childProcess> proc = + vmime::platformDependant::getHandler()-> + getChildProcessFactory()->create(path); + + proc->start(args, utility::childProcess::FLAG_REDIRECT_STDIN); + + // Copy message data from input stream to output pipe + utility::outputStream& os = *(proc->getStdIn()); + + // Workaround for lame sendmail implementations that + // can't handle CRLF eoln sequences: we transform CRLF + // sequences into LF characters. + utility::CRLFToLFFilteredOutputStream fos(os); + + // TODO: remove 'Bcc:' field from message header + + utility::bufferedStreamCopy(is, fos, size, progress); + + // Wait for sendmail to exit + proc->waitForFinish(); +} + + +// Service infos + +sendmailTransport::_infos sendmailTransport::sm_infos; + + +const serviceInfos& sendmailTransport::getInfosInstance() +{ + return (sm_infos); +} + + +const serviceInfos& sendmailTransport::getInfos() const +{ + return (sm_infos); +} + + +const string sendmailTransport::_infos::getPropertyPrefix() const +{ + return "transport.sendmail."; +} + + +const sendmailTransport::_infos::props& sendmailTransport::_infos::getProperties() const +{ + static props p = + { + // Path to sendmail (override default) + property("binpath", serviceInfos::property::TYPE_STRING, string(VMIME_SENDMAIL_PATH)) + }; + + return p; +} + + +const std::vector <serviceInfos::property> sendmailTransport::_infos::getAvailableProperties() const +{ + std::vector <property> list; + const props& p = getProperties(); + + list.push_back(p.PROPERTY_BINPATH); + + return (list); +} + + +} // sendmail +} // net +} // vmime + + +#endif // VMIME_BUILTIN_PLATFORM_POSIX diff --git a/src/net/service.cpp b/src/net/service.cpp new file mode 100644 index 00000000..9cf59d84 --- /dev/null +++ b/src/net/service.cpp @@ -0,0 +1,70 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/service.hpp" + +#include "vmime/net/defaultAuthenticator.hpp" + + +namespace vmime { +namespace net { + + +service::service(ref <session> sess, const serviceInfos& infos, ref <authenticator> auth) + : m_session(sess), m_auth(auth) +{ + if (!auth) + { + m_auth = vmime::create <defaultAuthenticator> + (sess, infos.getPropertyPrefix()); + } +} + + +service::~service() +{ +} + + +ref <const session> service::getSession() const +{ + return (m_session); +} + + +ref <session> service::getSession() +{ + return (m_session); +} + + +ref <const authenticator> service::getAuthenticator() const +{ + return (m_auth); +} + + +ref <authenticator> service::getAuthenticator() +{ + return (m_auth); +} + + +} // net +} // vmime diff --git a/src/net/serviceFactory.cpp b/src/net/serviceFactory.cpp new file mode 100644 index 00000000..43697cc8 --- /dev/null +++ b/src/net/serviceFactory.cpp @@ -0,0 +1,124 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/serviceFactory.hpp" +#include "vmime/net/service.hpp" + +#include "vmime/exception.hpp" +#include "vmime/config.hpp" + +#include "src/net/builtinServices.inl" + + +namespace vmime { +namespace net { + + +serviceFactory::serviceFactory() +{ +} + + +serviceFactory::~serviceFactory() +{ +} + + +serviceFactory* serviceFactory::getInstance() +{ + static serviceFactory instance; + return (&instance); +} + + +ref <service> serviceFactory::create + (ref <session> sess, const string& protocol, ref <authenticator> auth) +{ + return (getServiceByProtocol(protocol)->create(sess, auth)); +} + + +ref <service> serviceFactory::create + (ref <session> sess, const utility::url& u, ref <authenticator> auth) +{ + ref <service> serv = create(sess, u.getProtocol(), auth); + + sess->getProperties()[serv->getInfos().getPropertyPrefix() + "server.address"] = u.getHost(); + + if (u.getPort() != utility::url::UNSPECIFIED_PORT) + sess->getProperties()[serv->getInfos().getPropertyPrefix() + "server.port"] = u.getPort(); + + // Path portion of the URL is used to point a specific folder (empty = root). + // In maildir, this is used to point to the root of the message repository. + if (!u.getPath().empty()) + sess->getProperties()[serv->getInfos().getPropertyPrefix() + "server.rootpath"] = u.getPath(); + + if (!u.getUsername().empty()) + { + sess->getProperties()[serv->getInfos().getPropertyPrefix() + "auth.username"] = u.getUsername(); + sess->getProperties()[serv->getInfos().getPropertyPrefix() + "auth.password"] = u.getPassword(); + } + + return (serv); +} + + +ref <const serviceFactory::registeredService> serviceFactory::getServiceByProtocol(const string& protocol) const +{ + const string name(utility::stringUtils::toLower(protocol)); + + for (std::vector <ref <registeredService> >::const_iterator it = m_services.begin() ; + it != m_services.end() ; ++it) + { + if ((*it)->getName() == name) + return (*it); + } + + throw exceptions::no_service_available(name); +} + + +const int serviceFactory::getServiceCount() const +{ + return (m_services.size()); +} + + +ref <const serviceFactory::registeredService> serviceFactory::getServiceAt(const int pos) const +{ + return (m_services[pos]); +} + + +const std::vector <ref <const serviceFactory::registeredService> > serviceFactory::getServiceList() const +{ + std::vector <ref <const registeredService> > res; + + for (std::vector <ref <registeredService> >::const_iterator it = m_services.begin() ; + it != m_services.end() ; ++it) + { + res.push_back(*it); + } + + return (res); +} + + +} // net +} // vmime diff --git a/src/net/serviceInfos.cpp b/src/net/serviceInfos.cpp new file mode 100644 index 00000000..975e01e0 --- /dev/null +++ b/src/net/serviceInfos.cpp @@ -0,0 +1,150 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/serviceInfos.hpp" + + +namespace vmime { +namespace net { + + +// Common properties +const serviceInfos::property serviceInfos::property::SERVER_ADDRESS + ("server.address", serviceInfos::property::TYPE_STRING); + +const serviceInfos::property serviceInfos::property::SERVER_PORT + ("server.port", serviceInfos::property::TYPE_INTEGER); + +const serviceInfos::property serviceInfos::property::SERVER_ROOTPATH + ("server.rootpath", serviceInfos::property::TYPE_STRING); + +const serviceInfos::property serviceInfos::property::SERVER_SOCKETFACTORY + ("server.socket-factory", serviceInfos::property::TYPE_STRING, "default"); + +const serviceInfos::property serviceInfos::property::AUTH_USERNAME + ("auth.username", serviceInfos::property::TYPE_STRING); + +const serviceInfos::property serviceInfos::property::AUTH_PASSWORD + ("auth.password", serviceInfos::property::TYPE_STRING); + +const serviceInfos::property serviceInfos::property::TIMEOUT_FACTORY + ("timeout.factory", serviceInfos::property::TYPE_STRING); + + + +// serviceInfos + +serviceInfos::serviceInfos() +{ +} + + +serviceInfos::serviceInfos(const serviceInfos&) +{ +} + + +serviceInfos& serviceInfos::operator=(const serviceInfos&) +{ + return (*this); +} + + +serviceInfos::~serviceInfos() +{ +} + + +const bool serviceInfos::hasProperty(ref <session> s, const property& p) const +{ + return s->getProperties().hasProperty(getPropertyPrefix() + p.getName()); +} + + + +// serviceInfos::property + +serviceInfos::property::property + (const string& name, const Types type, + const string& defaultValue, const int flags) + : m_name(name), m_defaultValue(defaultValue), + m_type(type), m_flags(flags) +{ +} + + +serviceInfos::property::property + (const property& p, const int addFlags, const int removeFlags) +{ + m_name = p.m_name; + m_type = p.m_type; + m_defaultValue = p.m_defaultValue; + m_flags = (p.m_flags | addFlags) & ~removeFlags; +} + + +serviceInfos::property::property + (const property& p, const string& newDefaultValue, + const int addFlags, const int removeFlags) +{ + m_name = p.m_name; + m_type = p.m_type; + m_defaultValue = newDefaultValue; + m_flags = (p.m_flags | addFlags) & ~removeFlags; +} + + +serviceInfos::property& serviceInfos::property::operator=(const property& p) +{ + m_name = p.m_name; + m_type = p.m_type; + m_defaultValue = p.m_defaultValue; + m_flags = p.m_flags; + + return (*this); +} + + +const string& serviceInfos::property::getName() const +{ + return (m_name); +} + + +const string& serviceInfos::property::getDefaultValue() const +{ + return (m_defaultValue); +} + + +const serviceInfos::property::Types serviceInfos::property::getType() const +{ + return (m_type); +} + + +const int serviceInfos::property::getFlags() const +{ + return (m_flags); +} + + +} // net +} // vmime + diff --git a/src/net/session.cpp b/src/net/session.cpp new file mode 100644 index 00000000..2c46bf60 --- /dev/null +++ b/src/net/session.cpp @@ -0,0 +1,126 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/session.hpp" +#include "vmime/net/serviceFactory.hpp" + +#include "vmime/net/store.hpp" +#include "vmime/net/transport.hpp" + + +namespace vmime { +namespace net { + + +session::session() +{ +} + + +session::session(const session& sess) + : object(), m_props(sess.m_props) +{ +} + + +session::session(const propertySet& props) + : m_props(props) +{ +} + + +session::~session() +{ +} + + +ref <transport> session::getTransport(ref <authenticator> auth) +{ + return (getTransport(m_props["transport.protocol"], auth)); +} + + +ref <transport> session::getTransport(const string& protocol, ref <authenticator> auth) +{ + ref <session> sess = thisRef().dynamicCast <session>(); + ref <service> sv = serviceFactory::getInstance()->create(sess, protocol, auth); + + if (sv->getType() != service::TYPE_TRANSPORT) + throw exceptions::no_service_available(); + + return sv.staticCast <transport>(); +} + + +ref <transport> session::getTransport(const utility::url& url, ref <authenticator> auth) +{ + ref <session> sess = thisRef().dynamicCast <session>(); + ref <service> sv = serviceFactory::getInstance()->create(sess, url, auth); + + if (sv->getType() != service::TYPE_TRANSPORT) + throw exceptions::no_service_available(); + + return sv.staticCast <transport>(); +} + + +ref <store> session::getStore(ref <authenticator> auth) +{ + return (getStore(m_props["store.protocol"], auth)); +} + + +ref <store> session::getStore(const string& protocol, ref <authenticator> auth) +{ + ref <session> sess = thisRef().dynamicCast <session>(); + ref <service> sv = serviceFactory::getInstance()->create(sess, protocol, auth); + + if (sv->getType() != service::TYPE_STORE) + throw exceptions::no_service_available(); + + return sv.staticCast <store>(); +} + + +ref <store> session::getStore(const utility::url& url, ref <authenticator> auth) +{ + ref <session> sess = thisRef().dynamicCast <session>(); + ref <service> sv = serviceFactory::getInstance()->create(sess, url, auth); + + if (sv->getType() != service::TYPE_STORE) + throw exceptions::no_service_available(); + + return sv.staticCast <store>(); +} + + +const propertySet& session::getProperties() const +{ + return (m_props); +} + + +propertySet& session::getProperties() +{ + return (m_props); +} + + +} // net +} // vmime diff --git a/src/net/simpleAuthenticator.cpp b/src/net/simpleAuthenticator.cpp new file mode 100644 index 00000000..1b6db7b4 --- /dev/null +++ b/src/net/simpleAuthenticator.cpp @@ -0,0 +1,69 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/simpleAuthenticator.hpp" + + +namespace vmime { +namespace net { + + +simpleAuthenticator::simpleAuthenticator() +{ +} + + +simpleAuthenticator::simpleAuthenticator(const string& username, const string& password) + : m_username(username), m_password(password) +{ +} + + +const authenticationInfos simpleAuthenticator::getAuthInfos() const +{ + return (authenticationInfos(m_username, m_password)); +} + + +const string& simpleAuthenticator::getUsername() const +{ + return (m_username); +} + + +void simpleAuthenticator::setUsername(const string& username) +{ + m_username = username; +} + + +const string& simpleAuthenticator::getPassword() const +{ + return (m_password); +} + + +void simpleAuthenticator::setPassword(const string& password) +{ + m_password = password; +} + + +} // net +} // vmime diff --git a/src/net/smtp/SMTPTransport.cpp b/src/net/smtp/SMTPTransport.cpp new file mode 100644 index 00000000..1cbffcf5 --- /dev/null +++ b/src/net/smtp/SMTPTransport.cpp @@ -0,0 +1,503 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/smtp/SMTPTransport.hpp" + +#include "vmime/exception.hpp" +#include "vmime/platformDependant.hpp" +#include "vmime/encoderB64.hpp" +#include "vmime/mailboxList.hpp" + +#include "vmime/net/authHelper.hpp" + +#include "vmime/utility/filteredStream.hpp" + + +// Helpers for service properties +#define GET_PROPERTY(type, prop) \ + (sm_infos.getPropertyValue <type>(getSession(), sm_infos.getProperties().prop)) +#define HAS_PROPERTY(prop) \ + (sm_infos.hasProperty(getSession(), sm_infos.getProperties().prop)) + + +namespace vmime { +namespace net { +namespace smtp { + + +SMTPTransport::SMTPTransport(ref <session> sess, ref <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 = GET_PROPERTY(string, PROPERTY_SERVER_ADDRESS); + const port_t port = GET_PROPERTY(port_t, PROPERTY_SERVER_PORT); + + // Create the time-out handler + if (HAS_PROPERTY(PROPERTY_TIMEOUT_FACTORY)) + { + timeoutHandlerFactory* tof = platformDependant::getHandler()-> + getTimeoutHandlerFactory(GET_PROPERTY(string, PROPERTY_TIMEOUT_FACTORY)); + + m_timeoutHandler = tof->create(); + } + + // Create and connect the socket + socketFactory* sf = platformDependant::getHandler()-> + getSocketFactory(GET_PROPERTY(string, PROPERTY_SERVER_SOCKETFACTORY)); + + m_socket = sf->create(); + m_socket->connect(address, port); + + // Connection + // + // eg: C: <connection to server> + // --- 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 (GET_PROPERTY(bool, PROPERTY_OPTIONS_NEEDAUTH)) + { + 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(); + m_socket = NULL; + + 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); +} + + +void SMTPTransport::send(const mailbox& expeditor, const mailboxList& recipients, + utility::inputStream& is, const utility::stream::size_type size, + utility::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); + } + + // Stream copy with "\n." to "\n.." transformation + utility::outputStreamSocketAdapter sos(*m_socket); + utility::dotFilteredOutputStream fos(sos); + + utility::bufferedStreamCopy(is, fos, size, progress); + + // Send end-of-data delimiter + 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 string SMTPTransport::_infos::getPropertyPrefix() const +{ + return "transport.smtp."; +} + + +const SMTPTransport::_infos::props& SMTPTransport::_infos::getProperties() const +{ + static props p = + { + // SMTP-specific options + property("options.need-authentication", serviceInfos::property::TYPE_BOOL, "false"), + + // Common properties + property(serviceInfos::property::AUTH_USERNAME), + property(serviceInfos::property::AUTH_PASSWORD), + + property(serviceInfos::property::SERVER_ADDRESS, serviceInfos::property::FLAG_REQUIRED), + property(serviceInfos::property::SERVER_PORT, "25"), + property(serviceInfos::property::SERVER_SOCKETFACTORY), + + property(serviceInfos::property::TIMEOUT_FACTORY) + }; + + return p; +} + + +const std::vector <serviceInfos::property> SMTPTransport::_infos::getAvailableProperties() const +{ + std::vector <property> list; + const props& p = getProperties(); + + // SMTP-specific options + list.push_back(p.PROPERTY_OPTIONS_NEEDAUTH); + + // Common properties + list.push_back(p.PROPERTY_AUTH_USERNAME); + list.push_back(p.PROPERTY_AUTH_PASSWORD); + + list.push_back(p.PROPERTY_SERVER_ADDRESS); + list.push_back(p.PROPERTY_SERVER_PORT); + list.push_back(p.PROPERTY_SERVER_SOCKETFACTORY); + + list.push_back(p.PROPERTY_TIMEOUT_FACTORY); + + return (list); +} + + +} // smtp +} // net +} // vmime diff --git a/src/net/transport.cpp b/src/net/transport.cpp new file mode 100644 index 00000000..4f9acbeb --- /dev/null +++ b/src/net/transport.cpp @@ -0,0 +1,116 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/net/transport.hpp" + +#include "vmime/utility/stream.hpp" +#include "vmime/mailboxList.hpp" +#include "vmime/message.hpp" + + +namespace vmime { +namespace net { + + +transport::transport(ref <session> sess, const serviceInfos& infos, ref <authenticator> auth) + : service(sess, infos, auth) +{ +} + + +static void extractMailboxes + (mailboxList& recipients, const addressList& list) +{ + for (int i = 0 ; i < list.getAddressCount() ; ++i) + { + ref <mailbox> mbox = list.getAddressAt(i)->clone().dynamicCast <mailbox>(); + + if (mbox != NULL) + recipients.appendMailbox(mbox); + } +} + + +void transport::send(ref <vmime::message> msg, utility::progressionListener* progress) +{ + // Extract expeditor + mailbox expeditor; + + try + { + const mailboxField& from = dynamic_cast <const mailboxField&> + (*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 <const addressListField&> + (*msg->getHeader()->findField(fields::TO)); + extractMailboxes(recipients, to.getValue()); + } + catch (exceptions::no_such_field&) { } + + try + { + const addressListField& cc = dynamic_cast <const addressListField&> + (*msg->getHeader()->findField(fields::CC)); + extractMailboxes(recipients, cc.getValue()); + } + catch (exceptions::no_such_field&) { } + + try + { + const addressListField& bcc = dynamic_cast <const addressListField&> + (*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); +} + + +const transport::Type transport::getType() const +{ + return (TYPE_TRANSPORT); +} + + +} // net +} // vmime + diff --git a/src/platforms/posix/posixHandler.cpp b/src/platforms/posix/posixHandler.cpp index cd7f35fa..48d96984 100644 --- a/src/platforms/posix/posixHandler.cpp +++ b/src/platforms/posix/posixHandler.cpp @@ -166,14 +166,14 @@ const unsigned int posixHandler::getProcessId() const #if VMIME_HAVE_MESSAGING_FEATURES -vmime::messaging::socketFactory* posixHandler::getSocketFactory +vmime::net::socketFactory* posixHandler::getSocketFactory (const vmime::string& /* name */) const { return (m_socketFactory); } -vmime::messaging::timeoutHandlerFactory* posixHandler::getTimeoutHandlerFactory +vmime::net::timeoutHandlerFactory* posixHandler::getTimeoutHandlerFactory (const vmime::string& /* name */) const { // Not used by default diff --git a/src/platforms/posix/posixSocket.cpp b/src/platforms/posix/posixSocket.cpp index be346b2a..8a042950 100644 --- a/src/platforms/posix/posixSocket.cpp +++ b/src/platforms/posix/posixSocket.cpp @@ -172,7 +172,7 @@ void posixSocket::sendRaw(const char* buffer, const int count) // posixSocketFactory // -ref <vmime::messaging::socket> posixSocketFactory::create() +ref <vmime::net::socket> posixSocketFactory::create() { return vmime::create <posixSocket>(); } diff --git a/src/platforms/windows/windowsHandler.cpp b/src/platforms/windows/windowsHandler.cpp index d6f28037..19788642 100644 --- a/src/platforms/windows/windowsHandler.cpp +++ b/src/platforms/windows/windowsHandler.cpp @@ -233,14 +233,14 @@ const unsigned int windowsHandler::getProcessId() const #if VMIME_HAVE_MESSAGING_FEATURES -vmime::messaging::socketFactory* windowsHandler::getSocketFactory +vmime::net::socketFactory* windowsHandler::getSocketFactory (const vmime::string& /* name */) const { return (m_socketFactory); } -vmime::messaging::timeoutHandlerFactory* windowsHandler::getTimeoutHandlerFactory +vmime::net::timeoutHandlerFactory* windowsHandler::getTimeoutHandlerFactory (const vmime::string& /* name */) const { // Not used by default diff --git a/src/platforms/windows/windowsSocket.cpp b/src/platforms/windows/windowsSocket.cpp index b325eedd..bedfb07c 100644 --- a/src/platforms/windows/windowsSocket.cpp +++ b/src/platforms/windows/windowsSocket.cpp @@ -167,7 +167,7 @@ void windowsSocket::sendRaw(const char* buffer, const int count) // posixSocketFactory // -ref <vmime::messaging::socket> windowsSocketFactory::create() +ref <vmime::net::socket> windowsSocketFactory::create() { return vmime::create <windowsSocket>(); } diff --git a/src/utility/stream.cpp b/src/utility/stream.cpp index 799c6c32..49ebcf66 100644 --- a/src/utility/stream.cpp +++ b/src/utility/stream.cpp @@ -24,7 +24,7 @@ #include <iterator> // for std::back_inserter #if VMIME_HAVE_MESSAGING_FEATURES - #include "vmime/messaging/socket.hpp" + #include "vmime/net/socket.hpp" #endif @@ -331,7 +331,7 @@ const stream::size_type inputStreamPointerAdapter::skip(const size_type count) // outputStreamSocketAdapter -outputStreamSocketAdapter::outputStreamSocketAdapter(messaging::socket& sok) +outputStreamSocketAdapter::outputStreamSocketAdapter(net::socket& sok) : m_socket(sok) { } @@ -346,7 +346,7 @@ void outputStreamSocketAdapter::write // inputStreamSocketAdapter -inputStreamSocketAdapter::inputStreamSocketAdapter(messaging::socket& sok) +inputStreamSocketAdapter::inputStreamSocketAdapter(net::socket& sok) : m_socket(sok) { } |