diff options
Diffstat (limited to 'src/vmime/net')
146 files changed, 33377 insertions, 0 deletions
diff --git a/src/vmime/net/builtinServices.inl b/src/vmime/net/builtinServices.inl new file mode 100644 index 00000000..fa2f3fe3 --- /dev/null +++ b/src/vmime/net/builtinServices.inl @@ -0,0 +1,76 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + + +// Include registration helpers +#include "vmime/net/serviceRegistration.inl" + + +#ifndef VMIME_BUILDING_DOC + + +#if VMIME_HAVE_MESSAGING_PROTO_POP3 + #include "vmime/net/pop3/POP3Store.hpp" + REGISTER_SERVICE(pop3::POP3Store, pop3, TYPE_STORE); + + #if VMIME_HAVE_TLS_SUPPORT + #include "vmime/net/pop3/POP3SStore.hpp" + REGISTER_SERVICE(pop3::POP3SStore, pop3s, TYPE_STORE); + #endif // VMIME_HAVE_TLS_SUPPORT +#endif + + +#if VMIME_HAVE_MESSAGING_PROTO_SMTP + #include "vmime/net/smtp/SMTPTransport.hpp" + REGISTER_SERVICE(smtp::SMTPTransport, smtp, TYPE_TRANSPORT); + + #if VMIME_HAVE_TLS_SUPPORT + #include "vmime/net/smtp/SMTPSTransport.hpp" + REGISTER_SERVICE(smtp::SMTPSTransport, smtps, TYPE_TRANSPORT); + #endif // VMIME_HAVE_TLS_SUPPORT +#endif + + +#if VMIME_HAVE_MESSAGING_PROTO_IMAP + #include "vmime/net/imap/IMAPStore.hpp" + REGISTER_SERVICE(imap::IMAPStore, imap, TYPE_STORE); + + #if VMIME_HAVE_TLS_SUPPORT + #include "vmime/net/imap/IMAPSStore.hpp" + REGISTER_SERVICE(imap::IMAPSStore, imaps, TYPE_STORE); + #endif // VMIME_HAVE_TLS_SUPPORT +#endif + + +#if VMIME_HAVE_MESSAGING_PROTO_MAILDIR + #include "vmime/net/maildir/maildirStore.hpp" + REGISTER_SERVICE(maildir::maildirStore, maildir, TYPE_STORE); +#endif + +#if VMIME_HAVE_MESSAGING_PROTO_SENDMAIL + #include "vmime/net/sendmail/sendmailTransport.hpp" + REGISTER_SERVICE(sendmail::sendmailTransport, sendmail, TYPE_TRANSPORT); +#endif + + +#endif // VMIME_BUILDING_DOC diff --git a/src/vmime/net/connectionInfos.hpp b/src/vmime/net/connectionInfos.hpp new file mode 100644 index 00000000..6c86eeab --- /dev/null +++ b/src/vmime/net/connectionInfos.hpp @@ -0,0 +1,68 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_CONNECTIONINFOS_HPP_INCLUDED +#define VMIME_NET_CONNECTIONINFOS_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#include "vmime/object.hpp" + + +namespace vmime { +namespace net { + + +/** Information about the connection used by a service. + */ +class VMIME_EXPORT connectionInfos : public object +{ +public: + + /** Return the host to which the service is connected. + * + * @return server host name or address + */ + virtual const string getHost() const = 0; + + /** Return the port to which the service is connected. + * + * @return server port + */ + virtual port_t getPort() const = 0; +}; + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + +#endif // VMIME_NET_CONNECTIONINFOS_HPP_INCLUDED + diff --git a/src/vmime/net/defaultConnectionInfos.cpp b/src/vmime/net/defaultConnectionInfos.cpp new file mode 100644 index 00000000..335e8f6f --- /dev/null +++ b/src/vmime/net/defaultConnectionInfos.cpp @@ -0,0 +1,60 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#include "vmime/net/defaultConnectionInfos.hpp" + + +namespace vmime { +namespace net { + + +defaultConnectionInfos::defaultConnectionInfos(const string& host, const port_t port) + : m_host(host), m_port(port) +{ +} + + +const string defaultConnectionInfos::getHost() const +{ + return m_host; +} + + +port_t defaultConnectionInfos::getPort() const +{ + return m_port; +} + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + diff --git a/src/vmime/net/defaultConnectionInfos.hpp b/src/vmime/net/defaultConnectionInfos.hpp new file mode 100644 index 00000000..50673bbc --- /dev/null +++ b/src/vmime/net/defaultConnectionInfos.hpp @@ -0,0 +1,66 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_DEFAULTCONNECTIONINFOS_HPP_INCLUDED +#define VMIME_NET_DEFAULTCONNECTIONINFOS_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#include "vmime/net/connectionInfos.hpp" + + +namespace vmime { +namespace net { + + +/** Information about the connection used by a service. + */ +class VMIME_EXPORT defaultConnectionInfos : public connectionInfos +{ +public: + + defaultConnectionInfos(const string& host, const port_t port); + + const string getHost() const; + port_t getPort() const; + +private: + + string m_host; + port_t m_port; +}; + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + +#endif // VMIME_NET_DEFAULTCONNECTIONINFOS_HPP_INCLUDED + diff --git a/src/vmime/net/events.cpp b/src/vmime/net/events.cpp new file mode 100644 index 00000000..a19e1738 --- /dev/null +++ b/src/vmime/net/events.cpp @@ -0,0 +1,166 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#include "vmime/net/events.hpp" +#include "vmime/net/folder.hpp" + +#include <algorithm> + + +namespace vmime { +namespace net { +namespace events { + + +// +// event +// + +event::event() +{ +} + + +event::~event() +{ +} + + +// +// messageCountEvent +// + +const char* messageCountEvent::EVENT_CLASS = "messageCountEvent"; + + +messageCountEvent::messageCountEvent + (shared_ptr <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()); +} + + +shared_ptr <folder> messageCountEvent::getFolder() const { return (m_folder); } +messageCountEvent::Types messageCountEvent::getType() const { return (m_type); } +const std::vector <int>& messageCountEvent::getNumbers() const { return (m_nums); } + + +void messageCountEvent::dispatch(messageCountListener* listener) +{ + if (m_type == TYPE_ADDED) + listener->messagesAdded(dynamicCast <messageCountEvent>(shared_from_this())); + else + listener->messagesRemoved(dynamicCast <messageCountEvent>(shared_from_this())); +} + + +const char* messageCountEvent::getClass() const +{ + return EVENT_CLASS; +} + + +// +// messageChangedEvent +// + +const char* messageChangedEvent::EVENT_CLASS = "messageChangedEvent"; + + +messageChangedEvent::messageChangedEvent + (shared_ptr <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()); +} + + +shared_ptr <folder> messageChangedEvent::getFolder() const { return (m_folder); } +messageChangedEvent::Types messageChangedEvent::getType() const { return (m_type); } +const std::vector <int>& messageChangedEvent::getNumbers() const { return (m_nums); } + + +void messageChangedEvent::dispatch(messageChangedListener* listener) +{ + listener->messageChanged(dynamicCast <messageChangedEvent>(shared_from_this())); +} + + +const char* messageChangedEvent::getClass() const +{ + return EVENT_CLASS; +} + + +// +// folderEvent +// + +const char* folderEvent::EVENT_CLASS = "folderEvent"; + + +folderEvent::folderEvent + (shared_ptr <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) +{ +} + + +shared_ptr <folder> folderEvent::getFolder() const { return (m_folder); } +folderEvent::Types folderEvent::getType() const { return (m_type); } + + +void folderEvent::dispatch(folderListener* listener) +{ + switch (m_type) + { + case TYPE_CREATED: listener->folderCreated(dynamicCast <folderEvent>(shared_from_this())); break; + case TYPE_RENAMED: listener->folderRenamed(dynamicCast <folderEvent>(shared_from_this())); break; + case TYPE_DELETED: listener->folderDeleted(dynamicCast <folderEvent>(shared_from_this())); break; + } +} + + +const char* folderEvent::getClass() const +{ + return EVENT_CLASS; +} + + +} // events +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + diff --git a/src/vmime/net/events.hpp b/src/vmime/net/events.hpp new file mode 100644 index 00000000..a3e952d4 --- /dev/null +++ b/src/vmime/net/events.hpp @@ -0,0 +1,273 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_EVENTS_HPP_INCLUDED +#define VMIME_NET_EVENTS_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#include <vector> + +#include "vmime/utility/path.hpp" + + +namespace vmime { +namespace net { + +class folder; + +namespace events { + + +/** Event occurring on folders or messages. + */ + +class VMIME_EXPORT event : public object +{ +public: + + event(); + virtual ~event(); + + virtual const char* getClass() const = 0; +}; + + +/** Event about the message count in a folder. + */ + +class VMIME_EXPORT messageCountEvent : public event +{ +public: + + static const char* EVENT_CLASS; + + + enum Types + { + TYPE_ADDED, /**< New messages have been added. */ + TYPE_REMOVED /**< Messages have been expunged (renumbering). */ + }; + + + messageCountEvent(shared_ptr <folder> folder, const Types type, const std::vector <int>& nums); + + /** Return the folder in which messages have been added/removed. + * + * @return folder in which message count changed + */ + shared_ptr <folder> getFolder() const; + + /** Return the event type. + * + * @return event type (see messageCountEvent::Types) + */ + Types getType() const; + + /** Return the numbers of the messages that have been added/removed. + * + * @return a list of message numbers + */ + const std::vector <int>& getNumbers() const; + + /** Dispatch the event to the specified listener. + * + * @param listener listener to notify + */ + void dispatch(class messageCountListener* listener); + + + const char* getClass() const; + +private: + + shared_ptr <folder> m_folder; + const Types m_type; + std::vector <int> m_nums; +}; + + +/** Listener for events about the message count in a folder. + */ + +class VMIME_EXPORT messageCountListener +{ +protected: + + virtual ~messageCountListener() { } + +public: + + virtual void messagesAdded(shared_ptr <messageCountEvent> event) = 0; + virtual void messagesRemoved(shared_ptr <messageCountEvent> event) = 0; +}; + + +/** Event occuring on a message. + */ + +class VMIME_EXPORT messageChangedEvent : public event +{ +public: + + static const char* EVENT_CLASS; + + + enum Types + { + TYPE_FLAGS // flags changed + }; + + + messageChangedEvent(shared_ptr <folder> folder, const Types type, const std::vector <int>& nums); + + /** Return the folder in which messages have changed. + * + * @return folder in which message count changed + */ + shared_ptr <folder> getFolder() const; + + /** Return the event type. + * + * @return event type (see messageChangedEvent::Types) + */ + Types getType() const; + + /** Return the numbers of the messages that have changed. + * + * @return a list of message numbers + */ + const std::vector <int>& getNumbers() const; + + /** Dispatch the event to the specified listener. + * + * @param listener listener to notify + */ + void dispatch(class messageChangedListener* listener); + + + const char* getClass() const; + +private: + + shared_ptr <folder> m_folder; + const Types m_type; + std::vector <int> m_nums; +}; + + +/** Listener for events occuring on a message. + */ + +class VMIME_EXPORT messageChangedListener +{ +protected: + + virtual ~messageChangedListener() { } + +public: + + virtual void messageChanged(shared_ptr <messageChangedEvent> event) = 0; +}; + + +/** Event occuring on a folder. + */ + +class VMIME_EXPORT folderEvent : public event +{ +public: + + static const char* EVENT_CLASS; + + + enum Types + { + TYPE_CREATED, /**< A folder was created. */ + TYPE_DELETED, /**< A folder was deleted. */ + TYPE_RENAMED /**< A folder was renamed. */ + }; + + + folderEvent(shared_ptr <folder> folder, const Types type, const utility::path& oldPath, const utility::path& newPath); + + /** Return the folder on which the event occured. + * + * @return folder on which the event occured + */ + shared_ptr <folder> getFolder() const; + + /** Return the event type. + * + * @return event type (see folderEvent::Types) + */ + Types getType() const; + + /** Dispatch the event to the specified listener. + * + * @param listener listener to notify + */ + void dispatch(class folderListener* listener); + + + const char* getClass() const; + +private: + + shared_ptr <folder> m_folder; + const Types m_type; + const utility::path m_oldPath; + const utility::path m_newPath; +}; + + +/** Listener for events occuring on a folder. + */ + +class VMIME_EXPORT folderListener +{ +protected: + + virtual ~folderListener() { } + +public: + + virtual void folderCreated(shared_ptr <folderEvent> event) = 0; + virtual void folderRenamed(shared_ptr <folderEvent> event) = 0; + virtual void folderDeleted(shared_ptr <folderEvent> event) = 0; +}; + + +} // events +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + +#endif // VMIME_NET_EVENTS_HPP_INCLUDED diff --git a/src/vmime/net/fetchAttributes.cpp b/src/vmime/net/fetchAttributes.cpp new file mode 100644 index 00000000..85a41b8c --- /dev/null +++ b/src/vmime/net/fetchAttributes.cpp @@ -0,0 +1,95 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#include "vmime/net/fetchAttributes.hpp" + +#include "vmime/utility/stringUtils.hpp" + +#include <algorithm> + + +namespace vmime { +namespace net { + + +fetchAttributes::fetchAttributes() + : m_predefinedAttribs(0) +{ +} + + +fetchAttributes::fetchAttributes(const int attribs) + : m_predefinedAttribs(attribs) +{ +} + + +fetchAttributes::fetchAttributes(const fetchAttributes& attribs) + : object() +{ + m_predefinedAttribs = attribs.m_predefinedAttribs; + m_headers = attribs.m_headers; +} + + +void fetchAttributes::add(const int attribs) +{ + m_predefinedAttribs = attribs; +} + + +void fetchAttributes::add(const string& header) +{ + m_headers.push_back(utility::stringUtils::toLower(header)); +} + + +bool fetchAttributes::has(const int attribs) const +{ + return (m_predefinedAttribs & attribs) != 0; +} + + +bool fetchAttributes::has(const string& header) const +{ + return std::find(m_headers.begin(), m_headers.end(), utility::stringUtils::toLower(header)) != m_headers.end(); +} + + +const std::vector <string> fetchAttributes::getHeaderFields() const +{ + return m_headers; +} + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES diff --git a/src/vmime/net/fetchAttributes.hpp b/src/vmime/net/fetchAttributes.hpp new file mode 100644 index 00000000..d01e9f50 --- /dev/null +++ b/src/vmime/net/fetchAttributes.hpp @@ -0,0 +1,142 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_FETCHATTRIBUTES_HPP_INCLUDED +#define VMIME_NET_FETCHATTRIBUTES_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#include <vector> + +#include "vmime/types.hpp" + + +namespace vmime { +namespace net { + + +/** Holds a set of attributes to fetch for a message. + */ +class VMIME_EXPORT fetchAttributes : public object +{ +public: + + /** Predefined attributes that can be fetched. + */ + enum PredefinedFetchAttributes + { + ENVELOPE = (1 << 0), /**< Sender, recipients, date, subject. */ + STRUCTURE = (1 << 1), /**< MIME structure (body parts). */ + CONTENT_INFO = (1 << 2), /**< Top-level content type. */ + FLAGS = (1 << 3), /**< Message flags. */ + SIZE = (1 << 4), /**< Message size (exact or estimated). */ + FULL_HEADER = (1 << 5), /**< Full RFC-[2]822 header. */ + UID = (1 << 6), /**< Unique identifier (protocol specific). */ + IMPORTANCE = (1 << 7), /**< Header fields suitable for use with misc::importanceHelper. */ + + CUSTOM = (1 << 16) /**< Reserved for future use. */ + }; + + /** Constructs an empty fetchAttributes object. + */ + fetchAttributes(); + + /** Constructs a new fetchAttributes object by specifying one or more + * predefined objects. + * + * @param attribs one or more OR-ed values of the PredefinedFetchAttributes enum + */ + fetchAttributes(const int attribs); + + /** Constructs a new fetchAttributes object by copying an existing object. + * + * @param attribs object to copy + */ + fetchAttributes(const fetchAttributes& attribs); + + /** Adds the specified predefined attribute to the set of attributes to fetch. + * + * @param attribs one or more OR-ed values of the PredefinedFetchAttributes enum + */ + void add(const int attribs); + + /** Adds the specified header field to the set of attributes to fetch. + * Fetching custom header fields is not supported by all protocols. + * At this time, only IMAP supports this. + * + * @param header name of header field (eg. "X-Mailer") + */ + void add(const string& header); + + /** Returns true if the set contains the specified attribute(s). + * + * @param attribs one or more OR-ed values of the PredefinedFetchAttributes enum + * @return true if the specified attributes are to be fetched + */ + bool has(const int attribs) const; + + /** Returns true if the set contains the specified header field. + * + * @param header name of header field (eg. "X-Mailer") + * @return true if the specified header fields are to be fetched + */ + bool has(const string& header) const; + + /** Returns true if the set contains the specified attribute(s). + * + * \deprecated Use the has() methods instead + * + * @param attribs one or more OR-ed values of the PredefinedFetchAttributes enum + * @return true if the specified attributes are to be fetched + */ + VMIME_DEPRECATED inline bool operator&(const int attribs) const + { + return has(attribs); + } + + /** Returns a list of header fields to fetch. + * + * @return list of header names (eg. "X-Mailer") + */ + const std::vector <string> getHeaderFields() const; + +private: + + int m_predefinedAttribs; + std::vector <string> m_headers; +}; + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + + +#endif // VMIME_NET_FETCHATTRIBUTES_HPP_INCLUDED diff --git a/src/vmime/net/folder.cpp b/src/vmime/net/folder.cpp new file mode 100644 index 00000000..1d6f3140 --- /dev/null +++ b/src/vmime/net/folder.cpp @@ -0,0 +1,127 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#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(shared_ptr <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(shared_ptr <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(shared_ptr <events::folderEvent> event) +{ + for (std::list <events::folderListener*>::iterator + it = m_folderListeners.begin() ; it != m_folderListeners.end() ; ++it) + { + event->dispatch(*it); + } +} + + +void folder::notifyEvent(shared_ptr <events::event> event) +{ + if (event->getClass() == events::messageCountEvent::EVENT_CLASS) + { + notifyMessageCount(dynamicCast <events::messageCountEvent>(event)); + } + else if (event->getClass() == events::messageChangedEvent::EVENT_CLASS) + { + notifyMessageChanged(dynamicCast <events::messageChangedEvent>(event)); + } + else if (event->getClass() == events::folderEvent::EVENT_CLASS) + { + notifyFolder(dynamicCast <events::folderEvent>(event)); + } +} + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + diff --git a/src/vmime/net/folder.hpp b/src/vmime/net/folder.hpp new file mode 100644 index 00000000..38ba4597 --- /dev/null +++ b/src/vmime/net/folder.hpp @@ -0,0 +1,399 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_FOLDER_HPP_INCLUDED +#define VMIME_NET_FOLDER_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#include <vector> + +#include "vmime/types.hpp" +#include "vmime/dateTime.hpp" + +#include "vmime/message.hpp" +#include "vmime/net/message.hpp" +#include "vmime/net/messageSet.hpp" +#include "vmime/net/events.hpp" +#include "vmime/net/folderStatus.hpp" +#include "vmime/net/fetchAttributes.hpp" + +#include "vmime/utility/path.hpp" +#include "vmime/utility/stream.hpp" +#include "vmime/utility/progressListener.hpp" + + +namespace vmime { +namespace net { + + +class store; + + +/** Abstract representation of a folder in a message store. + */ + +class VMIME_EXPORT folder : public object +{ +protected: + + folder(const folder&) : object() { } + folder() { } + +public: + + virtual ~folder() { } + + /** Type used for fully qualified path name of a folder. + */ + typedef vmime::utility::path path; + + + /** Open mode. + */ + enum Modes + { + MODE_READ_ONLY, /**< Read-only mode (no modification to folder or messages is possible). */ + MODE_READ_WRITE /**< Full access mode (read and write). */ + }; + + /** Folder types. + */ + enum Types + { + TYPE_CONTAINS_FOLDERS = (1 << 0), /**< Folder can contain folders. */ + TYPE_CONTAINS_MESSAGES = (1 << 1), /**< Folder can contain messages. */ + + TYPE_UNDEFINED = 9999 /**< Used internally (this should not be returned + by the type() function). */ + }; + + /** Folder flags. + */ + enum Flags + { + FLAG_CHILDREN = (1 << 0), /**< Folder contains subfolders. */ + FLAG_NO_OPEN = (1 << 1), /**< Folder cannot be open. */ + + FLAG_UNDEFINED = 9999 /**< Used internally (this should not be returned + by the type() function). */ + }; + + /** Return the type of this folder. + * + * @return folder type (see folder::Types) + */ + virtual int getType() = 0; + + /** Return the flags of this folder. + * + * @return folder flags (see folder::Flags) + */ + virtual int getFlags() = 0; + + /** Return the mode in which the folder has been open. + * + * @return folder opening mode (see folder::Modes) + */ + virtual int getMode() const = 0; + + /** Return the name of this folder. + * + * @return folder name + */ + virtual const folder::path::component getName() const = 0; + + /** Return the fully qualified path name of this folder. + * + * @return absolute path of the folder + */ + virtual const folder::path getFullPath() const = 0; + + /** Open this folder. + * + * @param mode open mode (see folder::Modes) + * @param failIfModeIsNotAvailable if set to false and if the requested mode + * is not available, a more restricted mode will be selected automatically. + * If set to true and if the requested mode is not available, the opening + * will fail. + * @throw exceptions::net_exception if an error occurs + * @throw exceptions::folder_already_open if the folder is already open + * in the same session + */ + virtual void open(const int mode, bool failIfModeIsNotAvailable = false) = 0; + + /** Close this folder. + * + * @param expunge if set to true, deleted messages are expunged + * @throw exceptions::net_exception if an error occurs + */ + virtual void close(const bool expunge) = 0; + + /** Create this folder. + * + * @param type folder type (see folder::Types) + * @throw exceptions::net_exception if an error occurs + */ + virtual void create(const int type) = 0; + + /** Test whether this folder exists. + * + * @return true if the folder exists, false otherwise + */ + virtual bool exists() = 0; + + /** Delete this folder. + * The folder should be closed before attempting to delete it. + * + * @throw exceptions::net_exception if an error occurs + */ + virtual void destroy() = 0; + + /** Test whether this folder is open. + * + * @return true if the folder is open, false otherwise + */ + virtual bool isOpen() const = 0; + + /** Get a new reference to a message in this folder, given its number. + * + * @param num message sequence number + * @return a new object referencing the specified message + * @throw exceptions::net_exception if an error occurs + */ + virtual shared_ptr <message> getMessage(const int num) = 0; + + /** Get new references to messages in this folder, given either their + * sequence numbers or UIDs. + * + * To retrieve messages by their number, use: + * \code{.cpp} + * // Get messages from sequence number 5 to sequence number 8 (including) + * folder->getMessage(vmime::net::messageSet::byNumber(5, 8)); + * + * // Get all messages in the folder, starting from number 42 + * folder->getMessage(vmime::net::messageSet::byNumber(42, -1)); + * \endcode + * Or, to retrieve messages by their UID, use: + * \code{.cpp} + * // Get messages from UID 1000 to UID 1042 (including) + * folder->getMessage(vmime::net::messageSet::byUID(1000, 1042)); + * + * // Get message with UID 1042 + * folder->getMessage(vmime::net::messageSet::byUID(1042)); + * + * // Get all messages in the folder, starting from UID 1000 + * folder->getMessage(vmime::net::messageSet::byUID(1000, "*")); + * \endcode + * + * @param msgs index set of messages to retrieve + * @return new objects referencing the specified messages + * @throw exceptions::net_exception if an error occurs + */ + virtual std::vector <shared_ptr <message> > getMessages(const messageSet& msgs) = 0; + + /** Return the number of messages in this folder. + * + * @return number of messages in the folder + */ + virtual int getMessageCount() = 0; + + /** Get a new reference to a sub-folder in this folder. + * + * @param name sub-folder name + * @return a new object referencing the specified folder + * @throw exceptions::net_exception if an error occurs + */ + virtual shared_ptr <folder> getFolder(const folder::path::component& name) = 0; + + /** Get the list of all sub-folders in this folder. + * + * @param recursive if set to true, all the descendant are returned. + * If set to false, only the direct children are returned. + * @return list of sub-folders + * @throw exceptions::net_exception if an error occurs + */ + virtual std::vector <shared_ptr <folder> > getFolders(const bool recursive = false) = 0; + + /** Rename (move) this folder to another location. + * + * @param newPath new path of the folder + * @throw exceptions::net_exception if an error occurs + */ + virtual void rename(const folder::path& newPath) = 0; + + /** Remove one or more messages from this folder. + * + * @param msgs index set of messages to delete + * @throw exceptions::net_exception if an error occurs + */ + virtual void deleteMessages(const messageSet& msgs) = 0; + + /** Change the flags for one or more messages in this folder. + * + * @param msgs index set of messages on which to set the flags + * @param flags set of flags (see message::Flags) + * @param mode indicate how to treat old and new flags (see message::FlagsModes) + * @throw exceptions::net_exception if an error occurs + */ + virtual void setMessageFlags(const messageSet& msgs, const int flags, const int mode = message::FLAG_MODE_SET) = 0; + + /** Add a message to this folder. + * + * @param msg message to add (data: header + body) + * @param flags flags for the new message + * @param date date/time for the new message (if NULL, the current time is used) + * @param progress progress listener, or NULL if not used + * @throw exceptions::net_exception if an error occurs + */ + virtual void addMessage(shared_ptr <vmime::message> msg, const int flags = message::FLAG_UNDEFINED, vmime::datetime* date = NULL, utility::progressListener* progress = NULL) = 0; + + /** Add a message to this folder. + * + * @param is message to add (data: header + body) + * @param size size of the message to add (in bytes) + * @param flags flags for the new message + * @param date date/time for the new message (if NULL, the current time is used) + * @param progress progress listener, or NULL if not used + * @throw exceptions::net_exception if an error occurs + */ + virtual void addMessage(utility::inputStream& is, const size_t size, const int flags = message::FLAG_UNDEFINED, vmime::datetime* date = NULL, utility::progressListener* progress = NULL) = 0; + + /** Copy messages from this folder to another folder. + * + * @param dest destination folder path + * @param msgs index set of messages to copy + * @throw exceptions::net_exception if an error occurs + */ + virtual void copyMessages(const folder::path& dest, const messageSet& msgs) = 0; + + /** Request folder status without opening it. + * + * \deprecated Use the new getStatus() method + * + * @param count will receive the number of messages in the folder + * @param unseen will receive the number of unseen messages in the folder + * @throw exceptions::net_exception if an error occurs + */ + virtual void status(int& count, int& unseen) = 0; + + /** Request folder status without opening it. + * + * @return current folder status + * @throw exceptions::net_exception if an error occurs + */ + virtual shared_ptr <folderStatus> getStatus() = 0; + + /** Expunge deleted messages. + * + * @throw exceptions::net_exception if an error occurs + */ + virtual void expunge() = 0; + + /** Return a new folder object referencing the parent folder of this folder. + * + * @return parent folder object + */ + virtual shared_ptr <folder> getParent() = 0; + + /** Return a reference to the store to which this folder belongs. + * + * @return the store object to which this folder is attached + */ + virtual shared_ptr <const store> getStore() const = 0; + + /** Return a reference to the store to which this folder belongs. + * + * @return the store object to which this folder is attached + */ + virtual shared_ptr <store> getStore() = 0; + + /** Fetch objects for the specified messages. + * + * @param msg list of message sequence numbers + * @param attribs set of attributes to fetch + * @param progress progress listener, or NULL if not used + * @throw exceptions::net_exception if an error occurs + */ + virtual void fetchMessages(std::vector <shared_ptr <message> >& msg, const fetchAttributes& attribs, utility::progressListener* progress = NULL) = 0; + + /** Fetch objects for the specified message. + * + * @param msg the message + * @param attribs set of attributes to fetch + * @throw exceptions::net_exception if an error occurs + */ + virtual void fetchMessage(shared_ptr <message> msg, const fetchAttributes& attribs) = 0; + + /** Return the list of fetchable objects supported by + * the underlying protocol (see folder::fetchAttributes). + * + * @return list of supported fetchable objects + */ + virtual int getFetchCapabilities() const = 0; + + /** Return the sequence numbers of messages whose UID equal or greater than + * the specified UID. + * + * @param uid the uid of the first message + * @throw exceptions::net_exception if an error occurs + */ + virtual std::vector <int> getMessageNumbersStartingOnUID(const message::uid& uid) = 0; + + // Event listeners + void addMessageChangedListener(events::messageChangedListener* l); + void removeMessageChangedListener(events::messageChangedListener* l); + + void addMessageCountListener(events::messageCountListener* l); + void removeMessageCountListener(events::messageCountListener* l); + + void addFolderListener(events::folderListener* l); + void removeFolderListener(events::folderListener* l); + +protected: + + void notifyMessageChanged(shared_ptr <events::messageChangedEvent> event); + void notifyMessageCount(shared_ptr <events::messageCountEvent> event); + void notifyFolder(shared_ptr <events::folderEvent> event); + void notifyEvent(shared_ptr <events::event> event); + +private: + + std::list <events::messageChangedListener*> m_messageChangedListeners; + std::list <events::messageCountListener*> m_messageCountListeners; + std::list <events::folderListener*> m_folderListeners; +}; + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + +#endif // VMIME_NET_FOLDER_HPP_INCLUDED diff --git a/src/vmime/net/folderStatus.hpp b/src/vmime/net/folderStatus.hpp new file mode 100644 index 00000000..b94db052 --- /dev/null +++ b/src/vmime/net/folderStatus.hpp @@ -0,0 +1,74 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_FOLDERSTATUS_HPP_INCLUDED +#define VMIME_NET_FOLDERSTATUS_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#include "vmime/base.hpp" + + +namespace vmime { +namespace net { + + +/** Holds the status of a mail store folder. + */ + +class VMIME_EXPORT folderStatus : public object +{ +public: + + /** Returns the total number of messages in the folder. + * + * @return number of messages + */ + virtual unsigned int getMessageCount() const = 0; + + /** Returns the number of unseen messages in the folder. + * + * @return number of unseen messages + */ + virtual unsigned int getUnseenCount() const = 0; + + /** Clones this object. + * + * @return a copy of this object + */ + virtual shared_ptr <folderStatus> clone() const = 0; +}; + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + +#endif // VMIME_NET_FOLDERSTATUS_HPP_INCLUDED diff --git a/src/vmime/net/imap/IMAPConnection.cpp b/src/vmime/net/imap/IMAPConnection.cpp new file mode 100644 index 00000000..234c2b6a --- /dev/null +++ b/src/vmime/net/imap/IMAPConnection.cpp @@ -0,0 +1,814 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include "vmime/net/imap/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/platform.hpp" + +#include "vmime/utility/stringUtils.hpp" + +#include "vmime/net/defaultConnectionInfos.hpp" + +#if VMIME_HAVE_SASL_SUPPORT + #include "vmime/security/sasl/SASLContext.hpp" +#endif // VMIME_HAVE_SASL_SUPPORT + +#if VMIME_HAVE_TLS_SUPPORT + #include "vmime/net/tls/TLSSession.hpp" + #include "vmime/net/tls/TLSSecuredConnectionInfos.hpp" +#endif // VMIME_HAVE_TLS_SUPPORT + +#include <sstream> + + +// Helpers for service properties +#define GET_PROPERTY(type, prop) \ + (m_store.lock()->getInfos().getPropertyValue <type>(getSession(), \ + dynamic_cast <const IMAPServiceInfos&>(m_store.lock()->getInfos()).getProperties().prop)) +#define HAS_PROPERTY(prop) \ + (m_store.lock()->getInfos().hasProperty(getSession(), \ + dynamic_cast <const IMAPServiceInfos&>(m_store.lock()->getInfos()).getProperties().prop)) + + +namespace vmime { +namespace net { +namespace imap { + + +IMAPConnection::IMAPConnection(shared_ptr <IMAPStore> store, shared_ptr <security::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), + m_secured(false), m_firstTag(true), m_capabilitiesFetched(false), m_noModSeq(false) +{ +} + + +IMAPConnection::~IMAPConnection() +{ + try + { + if (isConnected()) + disconnect(); + else if (m_socket) + internalDisconnect(); + } + catch (vmime::exception&) + { + // Ignore + } +} + + +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); + + shared_ptr <IMAPStore> store = m_store.lock(); + + // Create the time-out handler + if (store->getTimeoutHandlerFactory()) + m_timeoutHandler = store->getTimeoutHandlerFactory()->create(); + + // Create and connect the socket + m_socket = store->getSocketFactory()->create(m_timeoutHandler); + +#if VMIME_HAVE_TLS_SUPPORT + if (store->isIMAPS()) // dedicated port/IMAPS + { + shared_ptr <tls::TLSSession> tlsSession = tls::TLSSession::create + (store->getCertificateVerifier(), + store->getSession()->getTLSProperties()); + + shared_ptr <tls::TLSSocket> tlsSocket = + tlsSession->getSocket(m_socket); + + m_socket = tlsSocket; + + m_secured = true; + m_cntInfos = make_shared <tls::TLSSecuredConnectionInfos>(address, port, tlsSession, tlsSocket); + } + else +#endif // VMIME_HAVE_TLS_SUPPORT + { + m_cntInfos = make_shared <defaultConnectionInfos>(address, port); + } + + m_socket->connect(address, port); + + + m_tag = make_shared <IMAPTag>(); + m_parser = make_shared <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 + + std::auto_ptr <IMAPParser::greeting> greet(m_parser->readGreeting()); + bool needAuth = false; + + if (greet->resp_cond_bye()) + { + internalDisconnect(); + throw exceptions::connection_greeting_error(greet->getErrorLog()); + } + else if (greet->resp_cond_auth()->condition() != IMAPParser::resp_cond_auth::PREAUTH) + { + needAuth = true; + } + + if (greet->resp_cond_auth()->resp_text()->resp_text_code() && + greet->resp_cond_auth()->resp_text()->resp_text_code()->capability_data()) + { + processCapabilityResponseData(greet->resp_cond_auth()->resp_text()->resp_text_code()->capability_data()); + } + +#if VMIME_HAVE_TLS_SUPPORT + // Setup secured connection, if requested + const bool tls = HAS_PROPERTY(PROPERTY_CONNECTION_TLS) + && GET_PROPERTY(bool, PROPERTY_CONNECTION_TLS); + const bool tlsRequired = HAS_PROPERTY(PROPERTY_CONNECTION_TLS_REQUIRED) + && GET_PROPERTY(bool, PROPERTY_CONNECTION_TLS_REQUIRED); + + if (!store->isIMAPS() && tls) // only if not IMAPS + { + try + { + startTLS(); + } + // Non-fatal error + catch (exceptions::command_error&) + { + if (tlsRequired) + { + m_state = STATE_NONE; + throw; + } + else + { + // TLS is not required, so don't bother + } + } + // Fatal error + catch (...) + { + m_state = STATE_NONE; + throw; + } + } +#endif // VMIME_HAVE_TLS_SUPPORT + + // Authentication + if (needAuth) + { + try + { + authenticate(); + } + catch (...) + { + m_state = STATE_NONE; + throw; + } + } + + // Get the hierarchy separator character + initHierarchySeparator(); + + // Switch to state "Authenticated" + setState(STATE_AUTHENTICATED); +} + + +void IMAPConnection::authenticate() +{ + getAuthenticator()->setService(m_store.lock()); + +#if VMIME_HAVE_SASL_SUPPORT + // First, try SASL authentication + if (GET_PROPERTY(bool, PROPERTY_OPTIONS_SASL)) + { + try + { + authenticateSASL(); + return; + } + catch (exceptions::authentication_error& e) + { + if (!GET_PROPERTY(bool, PROPERTY_OPTIONS_SASL_FALLBACK)) + { + // Can't fallback on normal authentication + internalDisconnect(); + throw e; + } + else + { + // Ignore, will try normal authentication + } + } + catch (exception& e) + { + internalDisconnect(); + throw e; + } + } +#endif // VMIME_HAVE_SASL_SUPPORT + + // Normal authentication + const string username = getAuthenticator()->getUsername(); + const string password = getAuthenticator()->getPassword(); + + send(true, "LOGIN " + IMAPUtils::quoteString(username) + + " " + IMAPUtils::quoteString(password), true); + + std::auto_ptr <IMAPParser::response> resp(m_parser->readResponse()); + + if (resp->isBad()) + { + internalDisconnect(); + throw exceptions::command_error("LOGIN", resp->getErrorLog()); + } + else if (resp->response_done()->response_tagged()-> + resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) + { + internalDisconnect(); + throw exceptions::authentication_error(resp->getErrorLog()); + } + + // Server capabilities may change when logged in + if (!processCapabilityResponseData(resp.get())) + invalidateCapabilities(); +} + + +#if VMIME_HAVE_SASL_SUPPORT + +void IMAPConnection::authenticateSASL() +{ + if (!dynamicCast <security::sasl::SASLAuthenticator>(getAuthenticator())) + throw exceptions::authentication_error("No SASL authenticator available."); + + const std::vector <string> capa = getCapabilities(); + std::vector <string> saslMechs; + + for (unsigned int i = 0 ; i < capa.size() ; ++i) + { + const string& x = capa[i]; + + if (x.length() > 5 && + (x[0] == 'A' || x[0] == 'a') && + (x[1] == 'U' || x[1] == 'u') && + (x[2] == 'T' || x[2] == 't') && + (x[3] == 'H' || x[3] == 'h') && + x[4] == '=') + { + saslMechs.push_back(string(x.begin() + 5, x.end())); + } + } + + if (saslMechs.empty()) + throw exceptions::authentication_error("No SASL mechanism available."); + + std::vector <shared_ptr <security::sasl::SASLMechanism> > mechList; + + shared_ptr <security::sasl::SASLContext> saslContext = + make_shared <security::sasl::SASLContext>(); + + for (unsigned int i = 0 ; i < saslMechs.size() ; ++i) + { + try + { + mechList.push_back + (saslContext->createMechanism(saslMechs[i])); + } + catch (exceptions::no_such_mechanism&) + { + // Ignore mechanism + } + } + + if (mechList.empty()) + throw exceptions::authentication_error("No SASL mechanism available."); + + // Try to suggest a mechanism among all those supported + shared_ptr <security::sasl::SASLMechanism> suggestedMech = + saslContext->suggestMechanism(mechList); + + if (!suggestedMech) + throw exceptions::authentication_error("Unable to suggest SASL mechanism."); + + // Allow application to choose which mechanisms to use + mechList = dynamicCast <security::sasl::SASLAuthenticator>(getAuthenticator())-> + getAcceptableMechanisms(mechList, suggestedMech); + + if (mechList.empty()) + throw exceptions::authentication_error("No SASL mechanism available."); + + // Try each mechanism in the list in turn + for (unsigned int i = 0 ; i < mechList.size() ; ++i) + { + shared_ptr <security::sasl::SASLMechanism> mech = mechList[i]; + + shared_ptr <security::sasl::SASLSession> saslSession = + saslContext->createSession("imap", getAuthenticator(), mech); + + saslSession->init(); + + send(true, "AUTHENTICATE " + mech->getName(), true); + + for (bool cont = true ; cont ; ) + { + std::auto_ptr <IMAPParser::response> resp(m_parser->readResponse()); + + if (resp->response_done() && + resp->response_done()->response_tagged() && + resp->response_done()->response_tagged()->resp_cond_state()-> + status() == IMAPParser::resp_cond_state::OK) + { + m_socket = saslSession->getSecuredSocket(m_socket); + return; + } + else + { + std::vector <IMAPParser::continue_req_or_response_data*> + respDataList = resp->continue_req_or_response_data(); + + string response; + bool hasResponse = false; + + for (unsigned int i = 0 ; i < respDataList.size() ; ++i) + { + if (respDataList[i]->continue_req()) + { + response = respDataList[i]->continue_req()->resp_text()->text(); + hasResponse = true; + break; + } + } + + if (!hasResponse) + { + cont = false; + continue; + } + + byte_t* challenge = 0; + size_t challengeLen = 0; + + byte_t* resp = 0; + size_t respLen = 0; + + try + { + // Extract challenge + saslContext->decodeB64(response, &challenge, &challengeLen); + + // Prepare response + saslSession->evaluateChallenge + (challenge, challengeLen, &resp, &respLen); + + // Send response + send(false, saslContext->encodeB64(resp, respLen), true); + + // Server capabilities may change when logged in + invalidateCapabilities(); + } + catch (exceptions::sasl_exception& e) + { + if (challenge) + { + delete [] challenge; + challenge = NULL; + } + + if (resp) + { + delete [] resp; + resp = NULL; + } + + // Cancel SASL exchange + send(false, "*", true); + } + catch (...) + { + if (challenge) + delete [] challenge; + + if (resp) + delete [] resp; + + throw; + } + + if (challenge) + delete [] challenge; + + if (resp) + delete [] resp; + } + } + } + + throw exceptions::authentication_error + ("Could not authenticate using SASL: all mechanisms failed."); +} + +#endif // VMIME_HAVE_SASL_SUPPORT + + +#if VMIME_HAVE_TLS_SUPPORT + +void IMAPConnection::startTLS() +{ + try + { + send(true, "STARTTLS", true); + + std::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) + { + throw exceptions::command_error + ("STARTTLS", resp->getErrorLog(), "bad response"); + } + + shared_ptr <tls::TLSSession> tlsSession = tls::TLSSession::create + (m_store.lock()->getCertificateVerifier(), + m_store.lock()->getSession()->getTLSProperties()); + + shared_ptr <tls::TLSSocket> tlsSocket = + tlsSession->getSocket(m_socket); + + tlsSocket->handshake(m_timeoutHandler); + + m_socket = tlsSocket; + m_parser->setSocket(m_socket); + + m_secured = true; + m_cntInfos = make_shared <tls::TLSSecuredConnectionInfos> + (m_cntInfos->getHost(), m_cntInfos->getPort(), tlsSession, tlsSocket); + + // " Once TLS has been started, the client MUST discard cached + // information about server capabilities and SHOULD re-issue the + // CAPABILITY command. This is necessary to protect against + // man-in-the-middle attacks which alter the capabilities list prior + // to STARTTLS. " (RFC-2595) + invalidateCapabilities(); + } + catch (exceptions::command_error&) + { + // Non-fatal error + throw; + } + catch (exception&) + { + // Fatal error + internalDisconnect(); + throw; + } +} + +#endif // VMIME_HAVE_TLS_SUPPORT + + +const std::vector <string> IMAPConnection::getCapabilities() +{ + if (!m_capabilitiesFetched) + fetchCapabilities(); + + return m_capabilities; +} + + +bool IMAPConnection::hasCapability(const string& capa) +{ + if (!m_capabilitiesFetched) + fetchCapabilities(); + + const string normCapa = utility::stringUtils::toUpper(capa); + + for (size_t i = 0, n = m_capabilities.size() ; i < n ; ++i) + { + if (m_capabilities[i] == normCapa) + return true; + } + + return false; +} + + +void IMAPConnection::invalidateCapabilities() +{ + m_capabilities.clear(); + m_capabilitiesFetched = false; +} + + +void IMAPConnection::fetchCapabilities() +{ + send(true, "CAPABILITY", true); + + std::auto_ptr <IMAPParser::response> resp(m_parser->readResponse()); + + if (resp->response_done()->response_tagged()-> + resp_cond_state()->status() == IMAPParser::resp_cond_state::OK) + { + processCapabilityResponseData(resp.get()); + } +} + + +bool IMAPConnection::processCapabilityResponseData(const IMAPParser::response* resp) +{ + const std::vector <IMAPParser::continue_req_or_response_data*>& respDataList = + resp->continue_req_or_response_data(); + + for (size_t i = 0 ; i < respDataList.size() ; ++i) + { + if (respDataList[i]->response_data() == NULL) + continue; + + const IMAPParser::capability_data* capaData = + respDataList[i]->response_data()->capability_data(); + + if (capaData == NULL) + continue; + + processCapabilityResponseData(capaData); + return true; + } + + return false; +} + + +void IMAPConnection::processCapabilityResponseData(const IMAPParser::capability_data* capaData) +{ + std::vector <string> res; + + std::vector <IMAPParser::capability*> caps = capaData->capabilities(); + + for (unsigned int j = 0 ; j < caps.size() ; ++j) + { + if (caps[j]->auth_type()) + res.push_back("AUTH=" + caps[j]->auth_type()->name()); + else + res.push_back(utility::stringUtils::toUpper(caps[j]->atom()->value())); + } + + m_capabilities = res; + m_capabilitiesFetched = true; +} + + +shared_ptr <security::authenticator> IMAPConnection::getAuthenticator() +{ + return m_auth; +} + + +bool IMAPConnection::isConnected() const +{ + return (m_socket && m_socket->isConnected() && + (m_state == STATE_AUTHENTICATED || m_state == STATE_SELECTED)); +} + + +bool IMAPConnection::isSecuredConnection() const +{ + return m_secured; +} + + +shared_ptr <connectionInfos> IMAPConnection::getConnectionInfos() const +{ + return m_cntInfos; +} + + +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; + + m_secured = false; + m_cntInfos = null; +} + + +void IMAPConnection::initHierarchySeparator() +{ + send(true, "LIST \"\" \"\"", true); + + std::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", resp->getErrorLog(), "bad response"); + } + + const std::vector <IMAPParser::continue_req_or_response_data*>& respDataList = + resp->continue_req_or_response_data(); + + bool found = false; + + for (unsigned int i = 0 ; !found && i < respDataList.size() ; ++i) + { + if (respDataList[i]->response_data() == NULL) + continue; + + const IMAPParser::mailbox_data* mailboxData = + static_cast <const IMAPParser::response_data*> + (respDataList[i]->response_data())->mailbox_data(); + + if (mailboxData == NULL || mailboxData->type() != IMAPParser::mailbox_data::LIST) + continue; + + if (mailboxData->mailbox_list()->quoted_char() != '\0') + { + m_hierarchySeparator = mailboxData->mailbox_list()->quoted_char(); + found = true; + } + } + + if (!found) // default + m_hierarchySeparator = '/'; +} + + +void IMAPConnection::send(bool tag, const string& what, bool end) +{ + if (tag && !m_firstTag) + ++(*m_tag); + +#if VMIME_DEBUG + std::ostringstream oss; + + if (tag) + { + oss << string(*m_tag); + oss << " "; + } + + oss << what; + + if (end) + oss << "\r\n"; + + m_socket->send(oss.str()); +#else + if (tag) + { + m_socket->send(*m_tag); + m_socket->send(" "); + } + + m_socket->send(what); + + if (end) + { + m_socket->send("\r\n"); + } +#endif + + if (tag) + m_firstTag = false; +} + + +void IMAPConnection::sendRaw(const byte_t* buffer, const size_t count) +{ + m_socket->sendRaw(buffer, count); +} + + +IMAPParser::response* IMAPConnection::readResponse(IMAPParser::literalHandler* lh) +{ + return (m_parser->readResponse(lh)); +} + + +IMAPConnection::ProtocolStates IMAPConnection::state() const +{ + return (m_state); +} + + +void IMAPConnection::setState(const ProtocolStates state) +{ + m_state = state; +} + + +char IMAPConnection::hierarchySeparator() const +{ + return (m_hierarchySeparator); +} + + +shared_ptr <const IMAPStore> IMAPConnection::getStore() const +{ + return m_store.lock(); +} + + +shared_ptr <IMAPStore> IMAPConnection::getStore() +{ + return m_store.lock(); +} + + +shared_ptr <session> IMAPConnection::getSession() +{ + return m_store.lock()->getSession(); +} + + +shared_ptr <const socket> IMAPConnection::getSocket() const +{ + return m_socket; +} + + +bool IMAPConnection::isMODSEQDisabled() const +{ + return m_noModSeq; +} + + +void IMAPConnection::disableMODSEQ() +{ + m_noModSeq = true; +} + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + diff --git a/src/vmime/net/imap/IMAPConnection.hpp b/src/vmime/net/imap/IMAPConnection.hpp new file mode 100644 index 00000000..b38d0c27 --- /dev/null +++ b/src/vmime/net/imap/IMAPConnection.hpp @@ -0,0 +1,163 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_IMAP_IMAPCONNECTION_HPP_INCLUDED +#define VMIME_NET_IMAP_IMAPCONNECTION_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include "vmime/net/socket.hpp" +#include "vmime/net/timeoutHandler.hpp" +#include "vmime/net/session.hpp" +#include "vmime/net/connectionInfos.hpp" + +#include "vmime/net/imap/IMAPParser.hpp" + +#include "vmime/security/authenticator.hpp" + + +namespace vmime { +namespace net { +namespace imap { + + +class IMAPTag; +class IMAPStore; + + +class VMIME_EXPORT IMAPConnection : public object +{ +public: + + IMAPConnection(shared_ptr <IMAPStore> store, shared_ptr <security::authenticator> auth); + ~IMAPConnection(); + + + void connect(); + bool isConnected() const; + void disconnect(); + + + enum ProtocolStates + { + STATE_NONE, + STATE_NON_AUTHENTICATED, + STATE_AUTHENTICATED, + STATE_SELECTED, + STATE_LOGOUT + }; + + ProtocolStates state() const; + void setState(const ProtocolStates state); + + + char hierarchySeparator() const; + + + void send(bool tag, const string& what, bool end); + void sendRaw(const byte_t* buffer, const size_t count); + + IMAPParser::response* readResponse(IMAPParser::literalHandler* lh = NULL); + + + shared_ptr <const IMAPStore> getStore() const; + shared_ptr <IMAPStore> getStore(); + + shared_ptr <session> getSession(); + + void fetchCapabilities(); + void invalidateCapabilities(); + const std::vector <string> getCapabilities(); + bool hasCapability(const string& capa); + + shared_ptr <security::authenticator> getAuthenticator(); + + bool isSecuredConnection() const; + shared_ptr <connectionInfos> getConnectionInfos() const; + + shared_ptr <const socket> getSocket() const; + + bool isMODSEQDisabled() const; + void disableMODSEQ(); + +private: + + void authenticate(); +#if VMIME_HAVE_SASL_SUPPORT + void authenticateSASL(); +#endif // VMIME_HAVE_SASL_SUPPORT + +#if VMIME_HAVE_TLS_SUPPORT + void startTLS(); +#endif // VMIME_HAVE_TLS_SUPPORT + + bool processCapabilityResponseData(const IMAPParser::response* resp); + void processCapabilityResponseData(const IMAPParser::capability_data* capaData); + + + weak_ptr <IMAPStore> m_store; + + shared_ptr <security::authenticator> m_auth; + + shared_ptr <socket> m_socket; + + shared_ptr <IMAPParser> m_parser; + + shared_ptr <IMAPTag> m_tag; + + char m_hierarchySeparator; + + ProtocolStates m_state; + + shared_ptr <timeoutHandler> m_timeoutHandler; + + bool m_secured; + shared_ptr <connectionInfos> m_cntInfos; + + bool m_firstTag; + + std::vector <string> m_capabilities; + bool m_capabilitiesFetched; + + bool m_noModSeq; + + + void internalDisconnect(); + + void initHierarchySeparator(); +}; + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + +#endif // VMIME_NET_IMAP_IMAPCONNECTION_HPP_INCLUDED diff --git a/src/vmime/net/imap/IMAPFolder.cpp b/src/vmime/net/imap/IMAPFolder.cpp new file mode 100644 index 00000000..fb98887c --- /dev/null +++ b/src/vmime/net/imap/IMAPFolder.cpp @@ -0,0 +1,1511 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include "vmime/net/imap/IMAPFolder.hpp" + +#include "vmime/net/imap/IMAPStore.hpp" +#include "vmime/net/imap/IMAPParser.hpp" +#include "vmime/net/imap/IMAPMessage.hpp" +#include "vmime/net/imap/IMAPUtils.hpp" +#include "vmime/net/imap/IMAPConnection.hpp" +#include "vmime/net/imap/IMAPFolderStatus.hpp" + +#include "vmime/message.hpp" + +#include "vmime/exception.hpp" + +#include "vmime/utility/outputStreamAdapter.hpp" + +#include <algorithm> +#include <sstream> + + +namespace vmime { +namespace net { +namespace imap { + + +IMAPFolder::IMAPFolder(const folder::path& path, shared_ptr <IMAPStore> store, const int type, const int flags) + : m_store(store), m_connection(store->connection()), m_path(path), + m_name(path.isEmpty() ? folder::path::component("") : path.getLastComponent()), m_mode(-1), + m_open(false), m_type(type), m_flags(flags) +{ + store->registerFolder(this); + + m_status = make_shared <IMAPFolderStatus>(); +} + + +IMAPFolder::~IMAPFolder() +{ + shared_ptr <IMAPStore> store = m_store.lock(); + + if (store) + { + if (m_open) + close(false); + + store->unregisterFolder(this); + } + else if (m_open) + { + m_connection = null; + onClose(); + } +} + + +int IMAPFolder::getMode() const +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + return (m_mode); +} + + +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); + } +} + + +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) +{ + shared_ptr <IMAPStore> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + + // Ensure this folder is not already open in the same session + for (std::list <IMAPFolder*>::iterator it = store->m_folders.begin() ; + it != store->m_folders.end() ; ++it) + { + if ((*it) != this && (*it)->getFullPath() == m_path) + throw exceptions::folder_already_open(); + } + + // Open a connection for this folder + shared_ptr <IMAPConnection> connection = + make_shared <IMAPConnection>(store, store->getAuthenticator()); + + try + { + connection->connect(); + + // Emit the "SELECT" command + // + // Example: C: A142 SELECT INBOX + // S: * 172 EXISTS + // S: * 1 RECENT + // S: * OK [UNSEEN 12] Message 12 is first unseen + // S: * OK [UIDVALIDITY 3857529045] UIDs valid + // S: * FLAGS (\Answered \Flagged \Deleted \Seen \Draft) + // S: * OK [PERMANENTFLAGS (\Deleted \Seen \*)] Limited + // S: A142 OK [READ-WRITE] SELECT completed + + std::ostringstream oss; + + if (mode == MODE_READ_ONLY) + oss << "EXAMINE "; + else + oss << "SELECT "; + + oss << IMAPUtils::quoteString(IMAPUtils::pathToString + (connection->hierarchySeparator(), getFullPath())); + + if (m_connection->hasCapability("CONDSTORE")) + oss << " (CONDSTORE)"; + + connection->send(true, oss.str(), true); + + // Read the response + std::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", + resp->getErrorLog(), "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", + resp->getErrorLog(), "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::NOMODSEQ: + + connection->disableMODSEQ(); + break; + + default: + + break; + } + } + } + // Untagged responses: FLAGS, EXISTS, RECENT (required) + else if (responseData->mailbox_data()) + { + switch (responseData->mailbox_data()->type()) + { + default: break; + + case IMAPParser::mailbox_data::FLAGS: + { + m_type = IMAPUtils::folderTypeFromFlags + (responseData->mailbox_data()->mailbox_flag_list()); + + m_flags = IMAPUtils::folderFlagsFromFlags + (responseData->mailbox_data()->mailbox_flag_list()); + + break; + } + + } + } + } + + processStatusUpdate(resp.get()); + + // 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) +{ + shared_ptr <IMAPStore> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + shared_ptr <IMAPConnection> oldConnection = m_connection; + + // Emit the "CLOSE" command to expunge messages marked + // as deleted (this is fastest than "EXPUNGE") + if (expunge) + { + if (m_mode == MODE_READ_ONLY) + throw exceptions::operation_not_supported(); + + oldConnection->send(true, "CLOSE", true); + } + + // Close this folder connection + oldConnection->disconnect(); + + // Now use default store connection + m_connection = m_store.lock()->connection(); + + m_open = false; + m_mode = -1; + + m_status = make_shared <IMAPFolderStatus>(); + + onClose(); +} + + +void IMAPFolder::onClose() +{ + for (std::vector <IMAPMessage*>::iterator it = m_messages.begin() ; + it != m_messages.end() ; ++it) + { + (*it)->onFolderClosed(); + } + + m_messages.clear(); +} + + +void IMAPFolder::create(const int type) +{ + shared_ptr <IMAPStore> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + else if (isOpen()) + throw exceptions::illegal_state("Folder is open"); + else if (exists()) + throw exceptions::illegal_state("Folder already exists"); + else if (!store->isValidFolderName(m_name)) + throw exceptions::invalid_folder_name(); + + // Emit the "CREATE" command + // + // Example: C: A003 CREATE owatagusiam/ + // S: A003 OK CREATE completed + // C: A004 CREATE owatagusiam/blurdybloop + // S: A004 OK CREATE completed + + string mailbox = IMAPUtils::pathToString + (m_connection->hierarchySeparator(), getFullPath()); + + if (type & TYPE_CONTAINS_FOLDERS) + mailbox += m_connection->hierarchySeparator(); + + std::ostringstream oss; + oss << "CREATE " << IMAPUtils::quoteString(mailbox); + + m_connection->send(true, oss.str(), true); + + + std::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", + resp->getErrorLog(), "bad response"); + } + + // Notify folder created + shared_ptr <events::folderEvent> event = + make_shared <events::folderEvent> + (dynamicCast <folder>(shared_from_this()), + events::folderEvent::TYPE_CREATED, m_path, m_path); + + notifyFolder(event); +} + + +void IMAPFolder::destroy() +{ + shared_ptr <IMAPStore> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + + if (isOpen()) + throw exceptions::illegal_state("Folder is open"); + + const string mailbox = IMAPUtils::pathToString + (m_connection->hierarchySeparator(), getFullPath()); + + std::ostringstream oss; + oss << "DELETE " << IMAPUtils::quoteString(mailbox); + + m_connection->send(true, oss.str(), true); + + + std::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("DELETE", + resp->getErrorLog(), "bad response"); + } + + // Notify folder deleted + shared_ptr <events::folderEvent> event = + make_shared <events::folderEvent> + (dynamicCast <folder>(shared_from_this()), + events::folderEvent::TYPE_DELETED, m_path, m_path); + + notifyFolder(event); +} + + +bool IMAPFolder::exists() +{ + shared_ptr <IMAPStore> store = m_store.lock(); + + if (!isOpen() && !store) + throw exceptions::illegal_state("Store disconnected"); + + return (testExistAndGetType() != TYPE_UNDEFINED); +} + + +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); + + + std::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", + resp->getErrorLog(), "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", + resp->getErrorLog(), "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); +} + + +bool IMAPFolder::isOpen() const +{ + return (m_open); +} + + +shared_ptr <message> IMAPFolder::getMessage(const int num) +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + if (num < 1 || num > m_status->getMessageCount()) + throw exceptions::message_not_found(); + + return make_shared <IMAPMessage>(dynamicCast <IMAPFolder>(shared_from_this()), num); +} + + +std::vector <shared_ptr <message> > IMAPFolder::getMessages(const messageSet& msgs) +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + if (msgs.isEmpty()) + return std::vector <shared_ptr <message> >(); + + std::vector <shared_ptr <message> > messages; + + if (msgs.isNumberSet()) + { + const std::vector <int> numbers = IMAPUtils::messageSetToNumberList(msgs); + + shared_ptr <IMAPFolder> thisFolder = dynamicCast <IMAPFolder>(shared_from_this()); + + for (std::vector <int>::const_iterator it = numbers.begin() ; it != numbers.end() ; ++it) + messages.push_back(make_shared <IMAPMessage>(thisFolder, *it)); + } + else if (msgs.isUIDSet()) + { + // C: . UID FETCH uuuu1,uuuu2,uuuu3 UID + // S: * nnnn1 FETCH (UID uuuu1) + // S: * nnnn2 FETCH (UID uuuu2) + // S: * nnnn3 FETCH (UID uuuu3) + // S: . OK UID FETCH completed + + // Prepare command and arguments + std::ostringstream cmd; + cmd.imbue(std::locale::classic()); + + cmd << "UID FETCH " << IMAPUtils::messageSetToSequenceSet(msgs) << " UID"; + + // Send the request + m_connection->send(true, cmd.str(), true); + + // Get the response + std::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("UID FETCH ... UID", resp->getErrorLog(), "bad response"); + } + + // Process the 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("UID FETCH ... UID", + resp->getErrorLog(), "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; + + // Get Process fetch response for this message + const int msgNum = static_cast <int>(messageData->number()); + message::uid msgUID; + + // Find UID in 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::UID) + { + msgUID = (*it)->unique_id()->value(); + break; + } + } + + if (!msgUID.empty()) + { + shared_ptr <IMAPFolder> thisFolder = dynamicCast <IMAPFolder>(shared_from_this()); + messages.push_back(make_shared <IMAPMessage>(thisFolder, msgNum, msgUID)); + } + } + } + + return messages; +} + + +int IMAPFolder::getMessageCount() +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + return m_status->getMessageCount(); +} + + +vmime_uint32 IMAPFolder::getUIDValidity() const +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + return m_status->getUIDValidity(); +} + + +vmime_uint64 IMAPFolder::getHighestModSequence() const +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + return m_status->getHighestModSeq(); +} + + +shared_ptr <folder> IMAPFolder::getFolder(const folder::path::component& name) +{ + shared_ptr <IMAPStore> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + + return make_shared <IMAPFolder>(m_path / name, store); +} + + +std::vector <shared_ptr <folder> > IMAPFolder::getFolders(const bool recursive) +{ + shared_ptr <IMAPStore> store = m_store.lock(); + + if (!isOpen() && !store) + throw exceptions::illegal_state("Store disconnected"); + + // Eg. List folders in '/foo/bar' + // + // C: a005 list "foo/bar" * + // S: * LIST (\NoSelect) "/" foo/bar + // S: * LIST (\NoInferiors) "/" foo/bar/zap + // S: a005 OK LIST completed + + 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); + + + std::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", resp->getErrorLog(), "bad response"); + } + + const std::vector <IMAPParser::continue_req_or_response_data*>& respDataList = + resp->continue_req_or_response_data(); + + + std::vector <shared_ptr <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", + resp->getErrorLog(), "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(make_shared <IMAPFolder>(path, store, + IMAPUtils::folderTypeFromFlags(mailbox_flag_list), + IMAPUtils::folderFlagsFromFlags(mailbox_flag_list))); + } + } + + return (v); +} + + +void IMAPFolder::fetchMessages(std::vector <shared_ptr <message> >& msg, const fetchAttributes& options, + utility::progressListener* progress) +{ + shared_ptr <IMAPStore> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + // Build message numbers list + std::vector <int> list; + list.reserve(msg.size()); + + std::map <int, shared_ptr <IMAPMessage> > numberToMsg; + + for (std::vector <shared_ptr <message> >::iterator it = msg.begin() ; it != msg.end() ; ++it) + { + list.push_back((*it)->getNumber()); + numberToMsg[(*it)->getNumber()] = dynamicCast <IMAPMessage>(*it); + } + + // Send the request + const string command = IMAPUtils::buildFetchRequest + (m_connection, messageSet::byNumber(list), options); + + m_connection->send(true, command, true); + + // Get the response + std::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("FETCH", + resp->getErrorLog(), "bad response"); + } + + const std::vector <IMAPParser::continue_req_or_response_data*>& respDataList = + resp->continue_req_or_response_data(); + + const size_t total = msg.size(); + size_t current = 0; + + if (progress) + progress->start(total); + + try + { + for (std::vector <IMAPParser::continue_req_or_response_data*>::const_iterator + it = respDataList.begin() ; it != respDataList.end() ; ++it) + { + if ((*it)->response_data() == NULL) + { + throw exceptions::command_error("FETCH", + resp->getErrorLog(), "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; + + // Process fetch response for this message + const int num = static_cast <int>(messageData->number()); + + std::map <int, shared_ptr <IMAPMessage> >::iterator msg = numberToMsg.find(num); + + if (msg != numberToMsg.end()) + { + (*msg).second->processFetchResponse(options, messageData); + + if (progress) + progress->progress(++current, total); + } + } + } + catch (...) + { + if (progress) + progress->stop(total); + + throw; + } + + if (progress) + progress->stop(total); + + processStatusUpdate(resp.get()); +} + + +void IMAPFolder::fetchMessage(shared_ptr <message> msg, const fetchAttributes& options) +{ + std::vector <shared_ptr <message> > msgs; + msgs.push_back(msg); + + fetchMessages(msgs, options, /* progress */ NULL); +} + + +int IMAPFolder::getFetchCapabilities() const +{ + return fetchAttributes::ENVELOPE | fetchAttributes::CONTENT_INFO | + fetchAttributes::STRUCTURE | fetchAttributes::FLAGS | + fetchAttributes::SIZE | fetchAttributes::FULL_HEADER | + fetchAttributes::UID | fetchAttributes::IMPORTANCE; +} + + +shared_ptr <folder> IMAPFolder::getParent() +{ + if (m_path.isEmpty()) + return null; + else + return make_shared <IMAPFolder>(m_path.getParent(), m_store.lock()); +} + + +shared_ptr <const store> IMAPFolder::getStore() const +{ + return m_store.lock(); +} + + +shared_ptr <store> IMAPFolder::getStore() +{ + return m_store.lock(); +} + + +void IMAPFolder::registerMessage(IMAPMessage* msg) +{ + m_messages.push_back(msg); +} + + +void IMAPFolder::unregisterMessage(IMAPMessage* msg) +{ + std::vector <IMAPMessage*>::iterator it = + std::find(m_messages.begin(), m_messages.end(), msg); + + if (it != m_messages.end()) + m_messages.erase(it); +} + + +void IMAPFolder::onStoreDisconnected() +{ + m_store.reset(); +} + + +void IMAPFolder::deleteMessages(const messageSet& msgs) +{ + shared_ptr <IMAPStore> store = m_store.lock(); + + if (msgs.isEmpty()) + throw exceptions::invalid_argument(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + else if (m_mode == MODE_READ_ONLY) + throw exceptions::illegal_state("Folder is read-only"); + + // Build the request text + std::ostringstream command; + command.imbue(std::locale::classic()); + + if (msgs.isUIDSet()) + command << "UID STORE " << IMAPUtils::messageSetToSequenceSet(msgs); + else + command << "STORE " << IMAPUtils::messageSetToSequenceSet(msgs); + + command << " +FLAGS (\\Deleted)"; + + // Send the request + m_connection->send(true, command.str(), true); + + // Get the response + std::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", + resp->getErrorLog(), "bad response"); + } + + processStatusUpdate(resp.get()); +} + + +void IMAPFolder::setMessageFlags(const messageSet& msgs, const int flags, const int mode) +{ + // Build the request text + std::ostringstream command; + command.imbue(std::locale::classic()); + + if (msgs.isUIDSet()) + command << "UID STORE " << IMAPUtils::messageSetToSequenceSet(msgs); + else + command << "STORE " << IMAPUtils::messageSetToSequenceSet(msgs); + + switch (mode) + { + case message::FLAG_MODE_ADD: command << " +FLAGS "; break; + case message::FLAG_MODE_REMOVE: command << " -FLAGS "; break; + default: + case message::FLAG_MODE_SET: command << " FLAGS "; 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 + std::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", + resp->getErrorLog(), "bad response"); + } + + processStatusUpdate(resp.get()); + } +} + + +void IMAPFolder::addMessage(shared_ptr <vmime::message> msg, const int flags, + vmime::datetime* date, utility::progressListener* progress) +{ + std::ostringstream oss; + utility::outputStreamAdapter ossAdapter(oss); + + msg->generate(ossAdapter); + + const string& str = oss.str(); + utility::inputStreamStringAdapter strAdapter(str); + + addMessage(strAdapter, str.length(), flags, date, progress); +} + + +void IMAPFolder::addMessage(utility::inputStream& is, const size_t size, const int flags, + vmime::datetime* date, utility::progressListener* progress) +{ + shared_ptr <IMAPStore> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + else if (m_mode == MODE_READ_ONLY) + throw exceptions::illegal_state("Folder is read-only"); + + // Build the request text + std::ostringstream command; + command.imbue(std::locale::classic()); + + 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 + std::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", + resp->getErrorLog(), "bad response"); + } + + // Send message data + const size_t total = size; + size_t current = 0; + + if (progress) + progress->start(total); + + const size_t blockSize = std::min(is.getBlockSize(), + static_cast <size_t>(m_connection->getSocket()->getBlockSize())); + + std::vector <byte_t> vbuffer(blockSize); + byte_t* buffer = &vbuffer.front(); + + while (!is.eof()) + { + // Read some data from the input stream + const size_t read = is.read(buffer, sizeof(buffer)); + current += read; + + // Put read data into socket output stream + m_connection->sendRaw(buffer, read); + + // Notify progress + if (progress) + progress->progress(current, total); + } + + m_connection->send(false, "", true); + + if (progress) + progress->stop(total); + + // Get the response + std::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", + resp->getErrorLog(), "bad response"); + } + + processStatusUpdate(resp.get()); +} + + +void IMAPFolder::expunge() +{ + shared_ptr <IMAPStore> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + else if (m_mode == MODE_READ_ONLY) + throw exceptions::illegal_state("Folder is read-only"); + + // Send the request + m_connection->send(true, "EXPUNGE", true); + + // Get the response + std::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", + resp->getErrorLog(), "bad response"); + } + + processStatusUpdate(resp.get()); +} + + +void IMAPFolder::rename(const folder::path& newPath) +{ + shared_ptr <IMAPStore> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + else if (m_path.isEmpty() || newPath.isEmpty()) + throw exceptions::illegal_operation("Cannot rename root folder"); + else if (m_path.getSize() == 1 && m_name.getBuffer() == "INBOX") + throw exceptions::illegal_operation("Cannot rename 'INBOX' folder"); + else if (!store->isValidFolderName(newPath.getLastComponent())) + throw exceptions::invalid_folder_name(); + + // Build the request text + std::ostringstream command; + command.imbue(std::locale::classic()); + + 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 + std::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", + resp->getErrorLog(), "bad response"); + } + + // Notify folder renamed + folder::path oldPath(m_path); + + m_path = newPath; + m_name = newPath.getLastComponent(); + + shared_ptr <events::folderEvent> event = + make_shared <events::folderEvent> + (dynamicCast <folder>(shared_from_this()), + events::folderEvent::TYPE_RENAMED, oldPath, newPath); + + notifyFolder(event); + + // Notify sub-folders + for (std::list <IMAPFolder*>::iterator it = store->m_folders.begin() ; + it != store->m_folders.end() ; ++it) + { + if ((*it) != this && oldPath.isParentOf((*it)->getFullPath())) + { + folder::path oldPath((*it)->m_path); + + (*it)->m_path.renameParent(oldPath, newPath); + + shared_ptr <events::folderEvent> event = + make_shared <events::folderEvent> + (dynamicCast <folder>((*it)->shared_from_this()), + events::folderEvent::TYPE_RENAMED, oldPath, (*it)->m_path); + + (*it)->notifyFolder(event); + } + } + + processStatusUpdate(resp.get()); +} + + +void IMAPFolder::copyMessages(const folder::path& dest, const messageSet& set) +{ + shared_ptr <IMAPStore> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + // Build the request text + std::ostringstream command; + command.imbue(std::locale::classic()); + + command << "COPY " << IMAPUtils::messageSetToSequenceSet(set) << " "; + command << IMAPUtils::quoteString(IMAPUtils::pathToString + (m_connection->hierarchySeparator(), dest)); + + // Send the request + m_connection->send(true, command.str(), true); + + // Get the response + std::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", + resp->getErrorLog(), "bad response"); + } + + processStatusUpdate(resp.get()); +} + + +void IMAPFolder::status(int& count, int& unseen) +{ + count = 0; + unseen = 0; + + shared_ptr <folderStatus> status = getStatus(); + + count = status->getMessageCount(); + unseen = status->getUnseenCount(); +} + + +shared_ptr <folderStatus> IMAPFolder::getStatus() +{ + shared_ptr <IMAPStore> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + + // Build the request text + std::ostringstream command; + command.imbue(std::locale::classic()); + + command << "STATUS "; + command << IMAPUtils::quoteString(IMAPUtils::pathToString + (m_connection->hierarchySeparator(), getFullPath())); + command << " ("; + + command << "MESSAGES" << ' ' << "UNSEEN" << ' ' << "UIDNEXT" << ' ' << "UIDVALIDITY"; + + if (m_connection->hasCapability("CONDSTORE")) + command << ' ' << "HIGHESTMODSEQ"; + + command << ")"; + + // Send the request + m_connection->send(true, command.str(), true); + + // Get the response + std::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("STATUS", + resp->getErrorLog(), "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) + { + const IMAPParser::response_data* responseData = (*it)->response_data(); + + if (responseData->mailbox_data() && + responseData->mailbox_data()->type() == IMAPParser::mailbox_data::STATUS) + { + shared_ptr <IMAPFolderStatus> status = make_shared <IMAPFolderStatus>(); + status->updateFromResponse(responseData->mailbox_data()); + + m_status->updateFromResponse(responseData->mailbox_data()); + + return status; + } + } + } + + throw exceptions::command_error("STATUS", + resp->getErrorLog(), "invalid response"); +} + + +void IMAPFolder::noop() +{ + shared_ptr <IMAPStore> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + + m_connection->send(true, "NOOP", true); + + std::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", resp->getErrorLog()); + } + + processStatusUpdate(resp.get()); +} + + +std::vector <int> IMAPFolder::getMessageNumbersStartingOnUID(const message::uid& uid) +{ + std::vector<int> v; + + std::ostringstream command; + command.imbue(std::locale::classic()); + + command << "SEARCH UID " << uid << ":*"; + + // Send the request + m_connection->send(true, command.str(), true); + + // Get the response + std::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("SEARCH", + resp->getErrorLog(), "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("SEARCH", + resp->getErrorLog(), "invalid response"); + } + + const IMAPParser::mailbox_data* mailboxData = + (*it)->response_data()->mailbox_data(); + + // We are only interested in responses of type "SEARCH" + if (mailboxData == NULL || + mailboxData->type() != IMAPParser::mailbox_data::SEARCH) + { + continue; + } + + for (std::vector <IMAPParser::nz_number*>::const_iterator + it = mailboxData->search_nz_number_list().begin() ; + it != mailboxData->search_nz_number_list().end(); + ++it) + { + v.push_back((*it)->value()); + } + } + + processStatusUpdate(resp.get()); + + return v; +} + + +void IMAPFolder::processStatusUpdate(const IMAPParser::response* resp) +{ + std::vector <shared_ptr <events::event> > events; + + shared_ptr <IMAPFolderStatus> oldStatus = vmime::clone(m_status); + int expungedMessageCount = 0; + + // Process tagged response + if (resp->response_done() && resp->response_done()->response_tagged() && + resp->response_done()->response_tagged() + ->resp_cond_state()->resp_text()->resp_text_code()) + { + const IMAPParser::resp_text_code* code = + resp->response_done()->response_tagged() + ->resp_cond_state()->resp_text()->resp_text_code(); + + m_status->updateFromResponse(code); + } + + // Process untagged responses + for (std::vector <IMAPParser::continue_req_or_response_data*>::const_iterator + it = resp->continue_req_or_response_data().begin() ; + it != resp->continue_req_or_response_data().end() ; ++it) + { + if ((*it)->response_data() && (*it)->response_data()->resp_cond_state() && + (*it)->response_data()->resp_cond_state()->resp_text()->resp_text_code()) + { + const IMAPParser::resp_text_code* code = + (*it)->response_data()->resp_cond_state()->resp_text()->resp_text_code(); + + m_status->updateFromResponse(code); + } + else if ((*it)->response_data() && (*it)->response_data()->mailbox_data()) + { + m_status->updateFromResponse((*it)->response_data()->mailbox_data()); + } + else if ((*it)->response_data() && (*it)->response_data()->message_data()) + { + const IMAPParser::message_data* msgData = (*it)->response_data()->message_data(); + const int msgNumber = msgData->number(); + + if ((*it)->response_data()->message_data()->type() == IMAPParser::message_data::FETCH) + { + // Message changed + for (std::vector <IMAPMessage*>::iterator mit = + m_messages.begin() ; mit != m_messages.end() ; ++mit) + { + if ((*mit)->getNumber() == msgNumber) + (*mit)->processFetchResponse(/* options */ 0, msgData); + } + + events.push_back(make_shared <events::messageChangedEvent> + (dynamicCast <folder>(shared_from_this()), + events::messageChangedEvent::TYPE_FLAGS, + std::vector <int>(1, msgNumber))); + } + else if ((*it)->response_data()->message_data()->type() == IMAPParser::message_data::EXPUNGE) + { + // A message has been expunged, renumber messages + for (std::vector <IMAPMessage*>::iterator jt = + m_messages.begin() ; jt != m_messages.end() ; ++jt) + { + if ((*jt)->getNumber() == msgNumber) + (*jt)->setExpunged(); + else if ((*jt)->getNumber() > msgNumber) + (*jt)->renumber((*jt)->getNumber() - 1); + } + + events.push_back(make_shared <events::messageCountEvent> + (dynamicCast <folder>(shared_from_this()), + events::messageCountEvent::TYPE_REMOVED, + std::vector <int>(1, msgNumber))); + + expungedMessageCount++; + } + } + } + + // New messages arrived + if (m_status->getMessageCount() > oldStatus->getMessageCount() - expungedMessageCount) + { + std::vector <int> newMessageNumbers; + + for (int msgNumber = oldStatus->getMessageCount() - expungedMessageCount ; + msgNumber <= m_status->getMessageCount() ; ++msgNumber) + { + newMessageNumbers.push_back(msgNumber); + } + + events.push_back(make_shared <events::messageCountEvent> + (dynamicCast <folder>(shared_from_this()), + events::messageCountEvent::TYPE_ADDED, + newMessageNumbers)); + } + + // Dispatch notifications + for (std::vector <shared_ptr <events::event> >::iterator evit = + events.begin() ; evit != events.end() ; ++evit) + { + notifyEvent(*evit); + } +} + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + diff --git a/src/vmime/net/imap/IMAPFolder.hpp b/src/vmime/net/imap/IMAPFolder.hpp new file mode 100644 index 00000000..cc7334ff --- /dev/null +++ b/src/vmime/net/imap/IMAPFolder.hpp @@ -0,0 +1,204 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_IMAP_IMAPFOLDER_HPP_INCLUDED +#define VMIME_NET_IMAP_IMAPFOLDER_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include <vector> +#include <map> + +#include "vmime/types.hpp" + +#include "vmime/net/folder.hpp" + +#include "vmime/net/imap/IMAPParser.hpp" + + +namespace vmime { +namespace net { +namespace imap { + + +class IMAPStore; +class IMAPMessage; +class IMAPConnection; +class IMAPFolderStatus; + + +/** IMAP folder implementation. + */ + +class VMIME_EXPORT IMAPFolder : public folder +{ +private: + + friend class IMAPStore; + friend class IMAPMessage; + + IMAPFolder(const IMAPFolder&); + +public: + + IMAPFolder(const folder::path& path, shared_ptr <IMAPStore> store, const int type = TYPE_UNDEFINED, const int flags = FLAG_UNDEFINED); + + ~IMAPFolder(); + + int getMode() const; + + int getType(); + + int getFlags(); + + const folder::path::component getName() const; + const folder::path getFullPath() const; + + void open(const int mode, bool failIfModeIsNotAvailable = false); + void close(const bool expunge); + void create(const int type); + + bool exists(); + + void destroy(); + + bool isOpen() const; + + shared_ptr <message> getMessage(const int num); + std::vector <shared_ptr <message> > getMessages(const messageSet& msgs); + + std::vector <int> getMessageNumbersStartingOnUID(const message::uid& uid); + + int getMessageCount(); + + shared_ptr <folder> getFolder(const folder::path::component& name); + std::vector <shared_ptr <folder> > getFolders(const bool recursive = false); + + void rename(const folder::path& newPath); + + void deleteMessages(const messageSet& msgs); + + void setMessageFlags(const messageSet& msgs, const int flags, const int mode = message::FLAG_MODE_SET); + + void addMessage(shared_ptr <vmime::message> msg, const int flags = message::FLAG_UNDEFINED, vmime::datetime* date = NULL, utility::progressListener* progress = NULL); + void addMessage(utility::inputStream& is, const size_t size, const int flags = message::FLAG_UNDEFINED, vmime::datetime* date = NULL, utility::progressListener* progress = NULL); + + void copyMessages(const folder::path& dest, const messageSet& msgs); + + void status(int& count, int& unseen); + shared_ptr <folderStatus> getStatus(); + + void noop(); + + void expunge(); + + shared_ptr <folder> getParent(); + + shared_ptr <const store> getStore() const; + shared_ptr <store> getStore(); + + + void fetchMessages(std::vector <shared_ptr <message> >& msg, const fetchAttributes& options, utility::progressListener* progress = NULL); + void fetchMessage(shared_ptr <message> msg, const fetchAttributes& options); + + int getFetchCapabilities() const; + + /** Returns the UID validity of the folder for the current session. + * If the server is capable of persisting UIDs accross sessions, + * this value should never change for a folder. If the UID validity + * differs across sessions, then the UIDs obtained during a previous + * session may not correspond to the UIDs of the same messages in + * this session. + * + * @return UID validity of the folder + */ + vmime_uint32 getUIDValidity() const; + + /** Returns the highest modification sequence of this folder, ie the + * modification sequence of the last message that changed in this + * folder. + * + * @return modification sequence, or zero if not supported by + * the underlying protocol + */ + vmime_uint64 getHighestModSequence() const; + +private: + + void registerMessage(IMAPMessage* msg); + void unregisterMessage(IMAPMessage* msg); + + void onStoreDisconnected(); + + void onClose(); + + int testExistAndGetType(); + + void setMessageFlagsImpl(const string& set, const int flags, const int mode); + + void copyMessagesImpl(const string& set, const folder::path& dest); + + + /** Process status updates ("unsolicited responses") contained in the + * specified response. Example: + * + * C: a006 NOOP + * S: * 930 EXISTS <-- this is a status update + * S: a006 OK Success + * + * @param resp parsed IMAP response + */ + void processStatusUpdate(const IMAPParser::response* resp); + + + weak_ptr <IMAPStore> m_store; + shared_ptr <IMAPConnection> m_connection; + + folder::path m_path; + folder::path::component m_name; + + int m_mode; + bool m_open; + + int m_type; + int m_flags; + + shared_ptr <IMAPFolderStatus> m_status; + + std::vector <IMAPMessage*> m_messages; +}; + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + +#endif // VMIME_NET_IMAP_IMAPFOLDER_HPP_INCLUDED diff --git a/src/vmime/net/imap/IMAPFolderStatus.cpp b/src/vmime/net/imap/IMAPFolderStatus.cpp new file mode 100644 index 00000000..c78a40f3 --- /dev/null +++ b/src/vmime/net/imap/IMAPFolderStatus.cpp @@ -0,0 +1,307 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include "vmime/net/imap/IMAPFolderStatus.hpp" + + +namespace vmime { +namespace net { +namespace imap { + + +IMAPFolderStatus::IMAPFolderStatus() + : m_count(0), + m_unseen(0), + m_recent(0), + m_uidValidity(0), + m_uidNext(0), + m_highestModSeq(0) +{ +} + + +IMAPFolderStatus::IMAPFolderStatus(const IMAPFolderStatus& other) + : folderStatus(), + m_count(other.m_count), + m_unseen(other.m_unseen), + m_recent(other.m_recent), + m_uidValidity(other.m_uidValidity), + m_uidNext(other.m_uidNext), + m_highestModSeq(other.m_highestModSeq) +{ +} + + +unsigned int IMAPFolderStatus::getMessageCount() const +{ + return m_count; +} + + +unsigned int IMAPFolderStatus::getUnseenCount() const +{ + return m_unseen; +} + + +unsigned int IMAPFolderStatus::getRecentCount() const +{ + return m_recent; +} + + +vmime_uint32 IMAPFolderStatus::getUIDValidity() const +{ + return m_uidValidity; +} + + +vmime_uint32 IMAPFolderStatus::getUIDNext() const +{ + return m_uidNext; +} + + +vmime_uint64 IMAPFolderStatus::getHighestModSeq() const +{ + return m_highestModSeq; +} + + +shared_ptr <folderStatus> IMAPFolderStatus::clone() const +{ + return make_shared <IMAPFolderStatus>(*this); +} + + +bool IMAPFolderStatus::updateFromResponse(const IMAPParser::mailbox_data* resp) +{ + bool changed = false; + + if (resp->type() == IMAPParser::mailbox_data::STATUS) + { + const IMAPParser::status_att_list* statusAttList = resp->status_att_list(); + + for (std::vector <IMAPParser::status_att_val*>::const_iterator + jt = statusAttList->values().begin() ; jt != statusAttList->values().end() ; ++jt) + { + switch ((*jt)->type()) + { + case IMAPParser::status_att_val::MESSAGES: + { + const unsigned int count = + static_cast <unsigned int>((*jt)->value_as_number()->value()); + + if (m_count != count) + { + m_count = count; + changed = true; + } + + break; + } + case IMAPParser::status_att_val::UNSEEN: + { + const unsigned int unseen = + static_cast <unsigned int>((*jt)->value_as_number()->value()); + + if (m_unseen != unseen) + { + m_unseen = unseen; + changed = true; + } + + break; + } + case IMAPParser::status_att_val::RECENT: + { + const unsigned int recent = + static_cast <unsigned int>((*jt)->value_as_number()->value()); + + if (m_recent != recent) + { + m_recent = recent; + changed = true; + } + + break; + } + case IMAPParser::status_att_val::UIDNEXT: + { + const vmime_uint32 uidNext = + static_cast <vmime_uint32>((*jt)->value_as_number()->value()); + + if (m_uidNext != uidNext) + { + m_uidNext = uidNext; + changed = true; + } + + break; + } + case IMAPParser::status_att_val::UIDVALIDITY: + { + const vmime_uint32 uidValidity = + static_cast <vmime_uint32>((*jt)->value_as_number()->value()); + + if (m_uidValidity != uidValidity) + { + m_uidValidity = uidValidity; + changed = true; + } + + break; + } + case IMAPParser::status_att_val::HIGHESTMODSEQ: + { + const vmime_uint64 highestModSeq = + static_cast <vmime_uint64>((*jt)->value_as_mod_sequence_value()->value()); + + if (m_highestModSeq != highestModSeq) + { + m_highestModSeq = highestModSeq; + changed = true; + } + + break; + } + + } + } + } + else if (resp->type() == IMAPParser::mailbox_data::EXISTS) + { + const unsigned int count = + static_cast <unsigned int>(resp->number()->value()); + + if (m_count != count) + { + m_count = count; + changed = true; + } + } + else if (resp->type() == IMAPParser::mailbox_data::RECENT) + { + const unsigned int recent = + static_cast <unsigned int>(resp->number()->value()); + + if (m_recent != recent) + { + m_recent = recent; + changed = true; + } + } + + return changed; +} + + +bool IMAPFolderStatus::updateFromResponse(const IMAPParser::resp_text_code* resp) +{ + bool changed = false; + + switch (resp->type()) + { + case IMAPParser::resp_text_code::UIDVALIDITY: + { + const vmime_uint32 uidValidity = + static_cast <vmime_uint32>(resp->nz_number()->value()); + + if (m_uidValidity != uidValidity) + { + m_uidValidity = uidValidity; + changed = true; + } + + break; + } + case IMAPParser::resp_text_code::UIDNEXT: + { + const vmime_uint32 uidNext = + static_cast <vmime_uint32>(resp->nz_number()->value()); + + if (m_uidNext != uidNext) + { + m_uidNext = uidNext; + changed = true; + } + + break; + } + case IMAPParser::resp_text_code::UNSEEN: + { + const unsigned int unseen = + static_cast <unsigned int>(resp->nz_number()->value()); + + if (m_unseen != unseen) + { + m_unseen = unseen; + changed = true; + } + + break; + } + case IMAPParser::resp_text_code::HIGHESTMODSEQ: + { + const vmime_uint64 highestModSeq = + static_cast <vmime_uint64>(resp->mod_sequence_value()->value()); + + if (m_highestModSeq != highestModSeq) + { + m_highestModSeq = highestModSeq; + changed = true; + } + + break; + } + case IMAPParser::resp_text_code::NOMODSEQ: + { + if (m_highestModSeq != 0) + { + m_highestModSeq = 0; + changed = true; + } + + break; + } + default: + + break; + } + + return changed; +} + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP diff --git a/src/vmime/net/imap/IMAPFolderStatus.hpp b/src/vmime/net/imap/IMAPFolderStatus.hpp new file mode 100644 index 00000000..03ca5937 --- /dev/null +++ b/src/vmime/net/imap/IMAPFolderStatus.hpp @@ -0,0 +1,124 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_IMAP_IMAPFOLDERSTATUS_HPP_INCLUDED +#define VMIME_NET_IMAP_IMAPFOLDERSTATUS_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include "vmime/net/folderStatus.hpp" + +#include "vmime/net/imap/IMAPParser.hpp" + + +namespace vmime { +namespace net { +namespace imap { + + +/** Holds the status of an IMAP folder. + */ + +class VMIME_EXPORT IMAPFolderStatus : public folderStatus +{ +public: + + IMAPFolderStatus(); + IMAPFolderStatus(const IMAPFolderStatus& other); + + // Inherited from folderStatus + unsigned int getMessageCount() const; + unsigned int getUnseenCount() const; + + shared_ptr <folderStatus> clone() const; + + /** Returns the the number of messages with the Recent flag set. + * + * @return number of messages flagged Recent + */ + unsigned int getRecentCount() const; + + /** Returns the UID validity of the folder for the current session. + * If the server is capable of persisting UIDs accross sessions, + * this value should never change for a folder. + * + * @return UID validity of the folder + */ + vmime_uint32 getUIDValidity() const; + + /** Returns the UID value that will be assigned to a new message + * in the folder. If the server does not support the UIDPLUS + * extension, it will return 0. + * + * @return UID of the next message + */ + vmime_uint32 getUIDNext() const; + + /** Returns the highest modification sequence of all messages + * in the folder, or 0 if not available for this folder, or not + * supported by the server. The server must support the CONDSTORE + * extension for this to be available. + * + * @return highest modification sequence + */ + vmime_uint64 getHighestModSeq() const; + + + /** Reads the folder status from the specified IMAP response. + * + * @param resp parsed IMAP response + * @return true if the status changed, or false otherwise + */ + bool updateFromResponse(const IMAPParser::mailbox_data* resp); + + /** Reads the folder status from the specified IMAP response. + * + * @param resp parsed IMAP response + * @return true if the status changed, or false otherwise + */ + bool updateFromResponse(const IMAPParser::resp_text_code* resp); + +private: + + unsigned int m_count; + unsigned int m_unseen; + unsigned int m_recent; + vmime_uint32 m_uidValidity; + vmime_uint32 m_uidNext; + vmime_uint64 m_highestModSeq; +}; + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + +#endif // VMIME_NET_IMAP_IMAPFOLDERSTATUS_HPP_INCLUDED diff --git a/src/vmime/net/imap/IMAPMessage.cpp b/src/vmime/net/imap/IMAPMessage.cpp new file mode 100644 index 00000000..c11aafc2 --- /dev/null +++ b/src/vmime/net/imap/IMAPMessage.cpp @@ -0,0 +1,649 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include "vmime/net/imap/IMAPParser.hpp" +#include "vmime/net/imap/IMAPMessage.hpp" +#include "vmime/net/imap/IMAPFolder.hpp" +#include "vmime/net/imap/IMAPFolderStatus.hpp" +#include "vmime/net/imap/IMAPStore.hpp" +#include "vmime/net/imap/IMAPConnection.hpp" +#include "vmime/net/imap/IMAPUtils.hpp" +#include "vmime/net/imap/IMAPMessageStructure.hpp" +#include "vmime/net/imap/IMAPMessagePart.hpp" +#include "vmime/net/imap/IMAPMessagePartContentHandler.hpp" + +#include "vmime/utility/outputStreamAdapter.hpp" + +#include <sstream> +#include <iterator> +#include <typeinfo> + + +namespace vmime { +namespace net { +namespace imap { + + +#ifndef VMIME_BUILDING_DOC + +// +// IMAPMessage_literalHandler +// + +class IMAPMessage_literalHandler : public IMAPParser::literalHandler +{ +public: + + IMAPMessage_literalHandler(utility::outputStream& os, utility::progressListener* 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::progressListener* m_progress; +}; + +#endif // VMIME_BUILDING_DOC + + + +// +// IMAPMessage +// + + +IMAPMessage::IMAPMessage(shared_ptr <IMAPFolder> folder, const int num) + : m_folder(folder), m_num(num), m_size(-1U), m_flags(FLAG_UNDEFINED), + m_expunged(false), m_modseq(0), m_structure(null) +{ + folder->registerMessage(this); +} + + +IMAPMessage::IMAPMessage(shared_ptr <IMAPFolder> folder, const int num, const uid& uid) + : m_folder(folder), m_num(num), m_size(-1), m_flags(FLAG_UNDEFINED), + m_expunged(false), m_uid(uid), m_modseq(0), m_structure(null) +{ + folder->registerMessage(this); +} + + +IMAPMessage::~IMAPMessage() +{ + shared_ptr <IMAPFolder> folder = m_folder.lock(); + + if (folder) + folder->unregisterMessage(this); +} + + +void IMAPMessage::onFolderClosed() +{ + m_folder.reset(); +} + + +int IMAPMessage::getNumber() const +{ + return (m_num); +} + + +const message::uid IMAPMessage::getUID() const +{ + return m_uid; +} + + +vmime_uint64 IMAPMessage::getModSequence() const +{ + return m_modseq; +} + + +size_t IMAPMessage::getSize() const +{ + if (m_size == -1U) + throw exceptions::unfetched_object(); + + return (m_size); +} + + +bool IMAPMessage::isExpunged() const +{ + return (m_expunged); +} + + +int IMAPMessage::getFlags() const +{ + if (m_flags == FLAG_UNDEFINED) + throw exceptions::unfetched_object(); + + return (m_flags); +} + + +shared_ptr <const messageStructure> IMAPMessage::getStructure() const +{ + if (m_structure == NULL) + throw exceptions::unfetched_object(); + + return m_structure; +} + + +shared_ptr <messageStructure> IMAPMessage::getStructure() +{ + if (m_structure == NULL) + throw exceptions::unfetched_object(); + + return m_structure; +} + + +shared_ptr <const header> IMAPMessage::getHeader() const +{ + if (m_header == NULL) + throw exceptions::unfetched_object(); + + return (m_header); +} + + +void IMAPMessage::extract + (utility::outputStream& os, + utility::progressListener* progress, + const size_t start, const size_t length, + const bool peek) const +{ + shared_ptr <const IMAPFolder> folder = m_folder.lock(); + + if (!folder) + throw exceptions::folder_not_found(); + + extractImpl(null, os, progress, start, length, + EXTRACT_HEADER | EXTRACT_BODY | (peek ? EXTRACT_PEEK : 0)); +} + + +void IMAPMessage::extractPart + (shared_ptr <const messagePart> p, + utility::outputStream& os, + utility::progressListener* progress, + const size_t start, const size_t length, + const bool peek) const +{ + shared_ptr <const IMAPFolder> folder = m_folder.lock(); + + if (!folder) + throw exceptions::folder_not_found(); + + extractImpl(p, os, progress, start, length, + EXTRACT_HEADER | EXTRACT_BODY | (peek ? EXTRACT_PEEK : 0)); +} + + +void IMAPMessage::fetchPartHeader(shared_ptr <messagePart> p) +{ + shared_ptr <IMAPFolder> folder = m_folder.lock(); + + if (!folder) + throw exceptions::folder_not_found(); + + std::ostringstream oss; + utility::outputStreamAdapter ossAdapter(oss); + + extractImpl(p, ossAdapter, NULL, 0, -1, EXTRACT_HEADER | EXTRACT_PEEK); + + dynamicCast <IMAPMessagePart>(p)->getOrCreateHeader().parse(oss.str()); +} + + +void IMAPMessage::fetchPartHeaderForStructure(shared_ptr <messageStructure> str) +{ + for (size_t i = 0, n = str->getPartCount() ; i < n ; ++i) + { + shared_ptr <messagePart> part = str->getPartAt(i); + + // Fetch header of current part + fetchPartHeader(part); + + // Fetch header of sub-parts + fetchPartHeaderForStructure(part->getStructure()); + } +} + + +void IMAPMessage::extractImpl + (shared_ptr <const messagePart> p, + utility::outputStream& os, + utility::progressListener* progress, + const size_t start, const size_t length, + const int extractFlags) const +{ + shared_ptr <const IMAPFolder> folder = m_folder.lock(); + + IMAPMessage_literalHandler literalHandler(os, progress); + + // Construct section identifier + std::ostringstream section; + section.imbue(std::locale::classic()); + + if (p != NULL) + { + shared_ptr <const IMAPMessagePart> currentPart = dynamicCast <const IMAPMessagePart>(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 + 1); + } + } + + // Build the request text + std::ostringstream command; + command.imbue(std::locale::classic()); + + if (m_uid.empty()) + command << "FETCH " << m_num << " BODY"; + else + command << "UID FETCH " << m_uid << " BODY"; + + /* + BODY[] header + body + BODY.PEEK[] header + body (peek) + BODY[HEADER] header + BODY.PEEK[HEADER] header (peek) + BODY[TEXT] body + BODY.PEEK[TEXT] body (peek) + */ + + if (extractFlags & EXTRACT_PEEK) + command << ".PEEK"; + + command << "["; + + if (section.str().empty()) + { + // header + body + if ((extractFlags & EXTRACT_HEADER) && (extractFlags & EXTRACT_BODY)) + command << ""; + // body only + else if (extractFlags & EXTRACT_BODY) + command << "TEXT"; + // header only + else if (extractFlags & EXTRACT_HEADER) + command << "HEADER"; + } + else + { + command << section.str(); + + // header + body + if ((extractFlags & EXTRACT_HEADER) && (extractFlags & EXTRACT_BODY)) + throw exceptions::operation_not_supported(); + // body only + else if (extractFlags & EXTRACT_BODY) + command << ".TEXT"; + // header only + else if (extractFlags & EXTRACT_HEADER) + command << ".MIME"; // "MIME" not "HEADER" for parts + } + + command << "]"; + + if (start != 0 || length != static_cast <size_t>(-1)) + command << "<" << start << "." << length << ">"; + + // Send the request + constCast <IMAPFolder>(folder)->m_connection->send(true, command.str(), true); + + // Get the response + std::auto_ptr <IMAPParser::response> resp + (constCast <IMAPFolder>(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", + resp->getErrorLog(), "bad response"); + } + + + if (extractFlags & EXTRACT_BODY) + { + // TODO: update the flags (eg. flag "\Seen" may have been set) + } +} + + +int IMAPMessage::processFetchResponse + (const fetchAttributes& options, const IMAPParser::message_data* msgData) +{ + shared_ptr <IMAPFolder> folder = m_folder.lock(); + + // Get message attributes + const std::vector <IMAPParser::msg_att_item*> atts = msgData->msg_att()->items(); + int changes = 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: + { + int flags = IMAPUtils::messageFlagsFromFlags((*it)->flag_list()); + + if (m_flags != flags) + { + m_flags = flags; + changes |= events::messageChangedEvent::TYPE_FLAGS; + } + + break; + } + case IMAPParser::msg_att_item::UID: + { + m_uid = (*it)->unique_id()->value(); + break; + } + case IMAPParser::msg_att_item::MODSEQ: + { + m_modseq = (*it)->mod_sequence_value()->value(); + break; + } + case IMAPParser::msg_att_item::ENVELOPE: + { + if (!options.has(fetchAttributes::FULL_HEADER)) + { + const IMAPParser::envelope* env = (*it)->envelope(); + shared_ptr <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; + IMAPUtils::convertAddressList(*(env->env_from()), from); + + if (!from.isEmpty()) + hdr->From()->setValue(*(from.getMailboxAt(0))); + + // To + mailboxList to; + IMAPUtils::convertAddressList(*(env->env_to()), to); + + hdr->To()->setValue(to.toAddressList()); + + // Sender + mailboxList sender; + IMAPUtils::convertAddressList(*(env->env_sender()), sender); + + if (!sender.isEmpty()) + hdr->Sender()->setValue(*(sender.getMailboxAt(0))); + + // Reply-to + mailboxList replyTo; + IMAPUtils::convertAddressList(*(env->env_reply_to()), replyTo); + + if (!replyTo.isEmpty()) + hdr->ReplyTo()->setValue(*(replyTo.getMailboxAt(0))); + + // Cc + mailboxList cc; + IMAPUtils::convertAddressList(*(env->env_cc()), cc); + + if (!cc.isEmpty()) + hdr->Cc()->setValue(cc); + + // Bcc + mailboxList bcc; + IMAPUtils::convertAddressList(*(env->env_bcc()), bcc); + + if (!bcc.isEmpty()) + hdr->Bcc()->setValue(bcc); + } + + break; + } + case IMAPParser::msg_att_item::BODY_STRUCTURE: + { + m_structure = make_shared <IMAPMessageStructure>((*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 = static_cast <size_t>((*it)->number()->value()); + break; + } + case IMAPParser::msg_att_item::BODY_SECTION: + { + if (!options.has(fetchAttributes::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 <shared_ptr <headerField> > fields = tempHeader.getFieldList(); + + for (std::vector <shared_ptr <headerField> >::const_iterator jt = fields.begin() ; + jt != fields.end() ; ++jt) + { + hdr.appendField(vmime::clone(*jt)); + } + } + } + + 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; + } + + } + } + + return changes; +} + + +shared_ptr <header> IMAPMessage::getOrCreateHeader() +{ + if (m_header != NULL) + return (m_header); + else + return (m_header = make_shared <header>()); +} + + +void IMAPMessage::setFlags(const int flags, const int mode) +{ + shared_ptr <IMAPFolder> folder = m_folder.lock(); + + if (!folder) + throw exceptions::folder_not_found(); + + if (!m_uid.empty()) + folder->setMessageFlags(messageSet::byUID(m_uid), flags, mode); + else + folder->setMessageFlags(messageSet::byNumber(m_num), flags, mode); +} + + +void IMAPMessage::constructParsedMessage + (shared_ptr <bodyPart> parentPart, shared_ptr <messageStructure> str, int level) +{ + if (level == 0) + { + shared_ptr <messagePart> part = str->getPartAt(0); + + // Copy header + shared_ptr <const header> hdr = part->getHeader(); + parentPart->getHeader()->copyFrom(*hdr); + + // Initialize body + parentPart->getBody()->setContents + (make_shared <IMAPMessagePartContentHandler> + (dynamicCast <IMAPMessage>(shared_from_this()), + part, parentPart->getBody()->getEncoding())); + + constructParsedMessage(parentPart, part->getStructure(), 1); + } + else + { + for (size_t i = 0, n = str->getPartCount() ; i < n ; ++i) + { + shared_ptr <messagePart> part = str->getPartAt(i); + + shared_ptr <bodyPart> childPart = make_shared <bodyPart>(); + + // Copy header + shared_ptr <const header> hdr = part->getHeader(); + childPart->getHeader()->copyFrom(*hdr); + + // Initialize body + childPart->getBody()->setContents + (make_shared <IMAPMessagePartContentHandler> + (dynamicCast <IMAPMessage>(shared_from_this()), + part, childPart->getBody()->getEncoding())); + + // Add child part + parentPart->getBody()->appendPart(childPart); + + // Construct sub parts + constructParsedMessage(childPart, part->getStructure(), ++level); + } + } +} + + +shared_ptr <vmime::message> IMAPMessage::getParsedMessage() +{ + // Fetch structure + shared_ptr <messageStructure> structure; + + try + { + structure = getStructure(); + } + catch (exceptions::unfetched_object&) + { + std::vector <shared_ptr <message> > msgs; + msgs.push_back(dynamicCast <IMAPMessage>(shared_from_this())); + + m_folder.lock()->fetchMessages + (msgs, fetchAttributes(fetchAttributes::STRUCTURE), /* progress */ NULL); + + structure = getStructure(); + } + + // Fetch header for each part + fetchPartHeaderForStructure(structure); + + // Construct message from structure + shared_ptr <vmime::message> msg = make_shared <vmime::message>(); + + constructParsedMessage(msg, structure); + + return msg; +} + + +void IMAPMessage::renumber(const int number) +{ + m_num = number; +} + + +void IMAPMessage::setExpunged() +{ + m_expunged = true; +} + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + diff --git a/src/vmime/net/imap/IMAPMessage.hpp b/src/vmime/net/imap/IMAPMessage.hpp new file mode 100644 index 00000000..92903d69 --- /dev/null +++ b/src/vmime/net/imap/IMAPMessage.hpp @@ -0,0 +1,191 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_IMAP_IMAPMESSAGE_HPP_INCLUDED +#define VMIME_NET_IMAP_IMAPMESSAGE_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include "vmime/net/message.hpp" +#include "vmime/net/folder.hpp" + +#include "vmime/net/imap/IMAPParser.hpp" + + +namespace vmime { +namespace net { +namespace imap { + + +class IMAPFolder; + + +/** IMAP message implementation. + */ + +class VMIME_EXPORT IMAPMessage : public message +{ +private: + + friend class IMAPFolder; + friend class IMAPMessagePartContentHandler; + + IMAPMessage(const IMAPMessage&) : message() { } + +public: + + IMAPMessage(shared_ptr <IMAPFolder> folder, const int num); + IMAPMessage(shared_ptr <IMAPFolder> folder, const int num, const uid& uid); + + ~IMAPMessage(); + + int getNumber() const; + + const uid getUID() const; + + /** Returns the modification sequence for this message. + * + * Every time metadata for this message changes, the modification + * sequence is updated, and is greater than the previous one. The + * server must support the CONDSTORE extension for this to be + * available. + * + * @return modification sequence, or zero if not supported by + * the underlying protocol + */ + vmime_uint64 getModSequence() const; + + size_t getSize() const; + + bool isExpunged() const; + + shared_ptr <const messageStructure> getStructure() const; + shared_ptr <messageStructure> getStructure(); + + shared_ptr <const header> getHeader() const; + + int getFlags() const; + void setFlags(const int flags, const int mode = FLAG_MODE_SET); + + void extract + (utility::outputStream& os, + utility::progressListener* progress = NULL, + const size_t start = 0, const size_t length = -1, + const bool peek = false) const; + + void extractPart + (shared_ptr <const messagePart> p, + utility::outputStream& os, + utility::progressListener* progress = NULL, + const size_t start = 0, const size_t length = -1, + const bool peek = false) const; + + void fetchPartHeader(shared_ptr <messagePart> p); + + shared_ptr <vmime::message> getParsedMessage(); + +private: + + /** Renumbers the message. + * + * @param number new sequence number + */ + void renumber(const int number); + + /** Marks the message as expunged. + */ + void setExpunged(); + + /** Processes the parsed response to fill in the attributes + * and metadata of this message. + * + * @param options one or more fetch options (see folder::fetchAttributes) + * @param msgData pointer to message_data component of the parsed response + * @return a combination of flags that specify what changed exactly on + * this message (see events::messageChangedEvent::Types) + */ + int processFetchResponse(const fetchAttributes& options, const IMAPParser::message_data* msgData); + + /** Recursively fetch part header for all parts in the structure. + * + * @param str structure for which to fetch parts headers + */ + void fetchPartHeaderForStructure(shared_ptr <messageStructure> str); + + /** Recursively contruct parsed message from structure. + * Called by getParsedMessage(). + * + * @param parentPart root body part (the message) + * @param str structure for which to construct part + * @param level current nesting level (0 is root) + */ + void constructParsedMessage(shared_ptr <bodyPart> parentPart, shared_ptr <messageStructure> str, int level = 0); + + + enum ExtractFlags + { + EXTRACT_HEADER = 0x1, + EXTRACT_BODY = 0x2, + EXTRACT_PEEK = 0x10 + }; + + void extractImpl + (shared_ptr <const messagePart> p, + utility::outputStream& os, + utility::progressListener* progress, + const size_t start, const size_t length, + const int extractFlags) const; + + + shared_ptr <header> getOrCreateHeader(); + + + void onFolderClosed(); + + weak_ptr <IMAPFolder> m_folder; + + int m_num; + size_t m_size; + int m_flags; + bool m_expunged; + uid m_uid; + vmime_uint64 m_modseq; + + shared_ptr <header> m_header; + shared_ptr <messageStructure> m_structure; +}; + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + +#endif // VMIME_NET_IMAP_IMAPMESSAGE_HPP_INCLUDED diff --git a/src/vmime/net/imap/IMAPMessagePart.cpp b/src/vmime/net/imap/IMAPMessagePart.cpp new file mode 100644 index 00000000..eed885dc --- /dev/null +++ b/src/vmime/net/imap/IMAPMessagePart.cpp @@ -0,0 +1,161 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include "vmime/net/imap/IMAPMessagePart.hpp" +#include "vmime/net/imap/IMAPMessageStructure.hpp" + + +namespace vmime { +namespace net { +namespace imap { + + +IMAPMessagePart::IMAPMessagePart(shared_ptr <IMAPMessagePart> 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()); +} + + +IMAPMessagePart::IMAPMessagePart(shared_ptr <IMAPMessagePart> 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; +} + + +shared_ptr <const messageStructure> IMAPMessagePart::getStructure() const +{ + if (m_structure != NULL) + return m_structure; + else + return IMAPMessageStructure::emptyStructure(); +} + + +shared_ptr <messageStructure> IMAPMessagePart::getStructure() +{ + if (m_structure != NULL) + return m_structure; + else + return IMAPMessageStructure::emptyStructure(); +} + + +shared_ptr <const IMAPMessagePart> IMAPMessagePart::getParent() const +{ + return m_parent.lock(); +} + + +const mediaType& IMAPMessagePart::getType() const +{ + return m_mediaType; +} + + +size_t IMAPMessagePart::getSize() const +{ + return m_size; +} + + +int IMAPMessagePart::getNumber() const +{ + return m_number; +} + + +shared_ptr <const header> IMAPMessagePart::getHeader() const +{ + if (m_header == NULL) + throw exceptions::unfetched_object(); + else + return m_header; +} + + +// static +shared_ptr <IMAPMessagePart> IMAPMessagePart::create + (shared_ptr <IMAPMessagePart> parent, const int number, const IMAPParser::body* body) +{ + if (body->body_type_mpart()) + { + shared_ptr <IMAPMessagePart> part = make_shared <IMAPMessagePart>(parent, number, body->body_type_mpart()); + part->m_structure = make_shared <IMAPMessageStructure>(part, body->body_type_mpart()->list()); + + return part; + } + else + { + return make_shared <IMAPMessagePart>(parent, number, body->body_type_1part()); + } +} + + +header& IMAPMessagePart::getOrCreateHeader() +{ + if (m_header != NULL) + return *m_header; + else + return *(m_header = make_shared <header>()); +} + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + diff --git a/src/vmime/net/imap/IMAPMessagePart.hpp b/src/vmime/net/imap/IMAPMessagePart.hpp new file mode 100644 index 00000000..af8581d7 --- /dev/null +++ b/src/vmime/net/imap/IMAPMessagePart.hpp @@ -0,0 +1,92 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_IMAP_IMAPMESSAGEPART_HPP_INCLUDED +#define VMIME_NET_IMAP_IMAPMESSAGEPART_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include "vmime/net/message.hpp" + +#include "vmime/net/imap/IMAPParser.hpp" + + +namespace vmime { +namespace net { +namespace imap { + + +class IMAPMessageStructure; + + +class VMIME_EXPORT IMAPMessagePart : public messagePart +{ +public: + + IMAPMessagePart(shared_ptr <IMAPMessagePart> parent, const int number, const IMAPParser::body_type_mpart* mpart); + IMAPMessagePart(shared_ptr <IMAPMessagePart> parent, const int number, const IMAPParser::body_type_1part* part); + + shared_ptr <const messageStructure> getStructure() const; + shared_ptr <messageStructure> getStructure(); + + shared_ptr <const IMAPMessagePart> getParent() const; + + const mediaType& getType() const; + size_t getSize() const; + int getNumber() const; + + shared_ptr <const header> getHeader() const; + + + static shared_ptr <IMAPMessagePart> create + (shared_ptr <IMAPMessagePart> parent, const int number, const IMAPParser::body* body); + + + header& getOrCreateHeader(); + +private: + + shared_ptr <IMAPMessageStructure> m_structure; + weak_ptr <IMAPMessagePart> m_parent; + shared_ptr <header> m_header; + + int m_number; + size_t m_size; + mediaType m_mediaType; +}; + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + +#endif // VMIME_NET_IMAP_IMAPMESSAGEPART_HPP_INCLUDED + diff --git a/src/vmime/net/imap/IMAPMessagePartContentHandler.cpp b/src/vmime/net/imap/IMAPMessagePartContentHandler.cpp new file mode 100644 index 00000000..1f53f082 --- /dev/null +++ b/src/vmime/net/imap/IMAPMessagePartContentHandler.cpp @@ -0,0 +1,216 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include "vmime/net/imap/IMAPMessagePartContentHandler.hpp" +#include "vmime/net/imap/IMAPFolder.hpp" +#include "vmime/net/imap/IMAPConnection.hpp" +#include "vmime/net/imap/IMAPFolderStatus.hpp" +#include "vmime/net/imap/IMAPStore.hpp" + +#include "vmime/utility/outputStreamAdapter.hpp" +#include "vmime/utility/inputStreamStringProxyAdapter.hpp" + + +namespace vmime { +namespace net { +namespace imap { + + +IMAPMessagePartContentHandler::IMAPMessagePartContentHandler + (shared_ptr <IMAPMessage> msg, shared_ptr <messagePart> part, const vmime::encoding& encoding) + : m_message(msg), m_part(part), m_encoding(encoding) +{ +} + + +shared_ptr <contentHandler> IMAPMessagePartContentHandler::clone() const +{ + return make_shared <IMAPMessagePartContentHandler> + (constCast <IMAPMessage>(m_message.lock()), + constCast <messagePart>(m_part.lock()), + m_encoding); +} + + +void IMAPMessagePartContentHandler::generate + (utility::outputStream& os, const vmime::encoding& enc, const size_t maxLineLength) const +{ + shared_ptr <IMAPMessage> msg = constCast <IMAPMessage>(m_message.lock()); + shared_ptr <messagePart> part = constCast <messagePart>(m_part.lock()); + + // Data is already encoded + if (isEncoded()) + { + // The data is already encoded but the encoding specified for + // the generation is different from the current one. We need + // to re-encode data: decode from input buffer to temporary + // buffer, and then re-encode to output stream... + if (m_encoding != enc) + { + // Extract part contents to temporary buffer + std::ostringstream oss; + utility::outputStreamAdapter tmp(oss); + + msg->extractPart(part, tmp, NULL); + + // Decode to another temporary buffer + utility::inputStreamStringProxyAdapter in(oss.str()); + + std::ostringstream oss2; + utility::outputStreamAdapter tmp2(oss2); + + shared_ptr <utility::encoder::encoder> theDecoder = m_encoding.getEncoder(); + theDecoder->decode(in, tmp2); + + // Reencode to output stream + string str = oss2.str(); + utility::inputStreamStringAdapter tempIn(str); + + shared_ptr <utility::encoder::encoder> theEncoder = enc.getEncoder(); + theEncoder->getProperties()["maxlinelength"] = maxLineLength; + theEncoder->getProperties()["text"] = (m_contentType.getType() == mediaTypes::TEXT); + + theEncoder->encode(tempIn, os); + } + // No encoding to perform + else + { + msg->extractPart(part, os); + } + } + // Need to encode data before + else + { + // Extract part contents to temporary buffer + std::ostringstream oss; + utility::outputStreamAdapter tmp(oss); + + msg->extractPart(part, tmp, NULL); + + // Encode temporary buffer to output stream + shared_ptr <utility::encoder::encoder> theEncoder = enc.getEncoder(); + theEncoder->getProperties()["maxlinelength"] = maxLineLength; + theEncoder->getProperties()["text"] = (m_contentType.getType() == mediaTypes::TEXT); + + utility::inputStreamStringAdapter is(oss.str()); + + theEncoder->encode(is, os); + } +} + + +void IMAPMessagePartContentHandler::extract + (utility::outputStream& os, utility::progressListener* progress) const +{ + shared_ptr <IMAPMessage> msg = constCast <IMAPMessage>(m_message.lock()); + shared_ptr <messagePart> part = constCast <messagePart>(m_part.lock()); + + // No decoding to perform + if (!isEncoded()) + { + msg->extractImpl(part, os, progress, 0, -1, IMAPMessage::EXTRACT_BODY); + } + // Need to decode data + else + { + // Extract part contents to temporary buffer + std::ostringstream oss; + utility::outputStreamAdapter tmp(oss); + + msg->extractImpl(part, tmp, NULL, 0, -1, IMAPMessage::EXTRACT_BODY); + + // Encode temporary buffer to output stream + utility::inputStreamStringAdapter is(oss.str()); + utility::progressListenerSizeAdapter plsa(progress, getLength()); + + shared_ptr <utility::encoder::encoder> theDecoder = m_encoding.getEncoder(); + theDecoder->decode(is, os, &plsa); + } +} + + +void IMAPMessagePartContentHandler::extractRaw + (utility::outputStream& os, utility::progressListener* progress) const +{ + shared_ptr <IMAPMessage> msg = constCast <IMAPMessage>(m_message.lock()); + shared_ptr <messagePart> part = constCast <messagePart>(m_part.lock()); + + msg->extractPart(part, os, progress); +} + + +size_t IMAPMessagePartContentHandler::getLength() const +{ + return m_part.lock()->getSize(); +} + + +bool IMAPMessagePartContentHandler::isEncoded() const +{ + return m_encoding != NO_ENCODING; +} + + +const vmime::encoding& IMAPMessagePartContentHandler::getEncoding() const +{ + return m_encoding; +} + + +bool IMAPMessagePartContentHandler::isEmpty() const +{ + return getLength() == 0; +} + + +bool IMAPMessagePartContentHandler::isBuffered() const +{ + return true; +} + + +void IMAPMessagePartContentHandler::setContentTypeHint(const mediaType& type) +{ + m_contentType = type; +} + + +const mediaType IMAPMessagePartContentHandler::getContentTypeHint() const +{ + return m_contentType; +} + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + diff --git a/src/vmime/net/imap/IMAPMessagePartContentHandler.hpp b/src/vmime/net/imap/IMAPMessagePartContentHandler.hpp new file mode 100644 index 00000000..cb52b2e3 --- /dev/null +++ b/src/vmime/net/imap/IMAPMessagePartContentHandler.hpp @@ -0,0 +1,87 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_IMAP_IMAPMESSAGEPARTCONTENTHANDLER_HPP_INCLUDED +#define VMIME_NET_IMAP_IMAPMESSAGEPARTCONTENTHANDLER_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include "vmime/contentHandler.hpp" +#include "vmime/net/imap/IMAPMessage.hpp" + + +namespace vmime { +namespace net { +namespace imap { + + +class VMIME_EXPORT IMAPMessagePartContentHandler : public contentHandler +{ +public: + + IMAPMessagePartContentHandler(shared_ptr <IMAPMessage> msg, shared_ptr <messagePart> part, const vmime::encoding& encoding); + + shared_ptr <contentHandler> clone() const; + + void generate(utility::outputStream& os, const vmime::encoding& enc, const size_t maxLineLength = lineLengthLimits::infinite) const; + + void extract(utility::outputStream& os, utility::progressListener* progress = NULL) const; + void extractRaw(utility::outputStream& os, utility::progressListener* progress = NULL) const; + + size_t getLength() const; + + bool isEncoded() const; + + const vmime::encoding& getEncoding() const; + + bool isEmpty() const; + + bool isBuffered() const; + + void setContentTypeHint(const mediaType& type); + const mediaType getContentTypeHint() const; + +private: + + weak_ptr <IMAPMessage> m_message; + weak_ptr <messagePart> m_part; + + vmime::encoding m_encoding; + vmime::mediaType m_contentType; +}; + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + +#endif // VMIME_NET_IMAP_IMAPMESSAGEPARTCONTENTHANDLER_HPP_INCLUDED + diff --git a/src/vmime/net/imap/IMAPMessageStructure.cpp b/src/vmime/net/imap/IMAPMessageStructure.cpp new file mode 100644 index 00000000..8dc333e9 --- /dev/null +++ b/src/vmime/net/imap/IMAPMessageStructure.cpp @@ -0,0 +1,94 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include "vmime/net/imap/IMAPMessageStructure.hpp" +#include "vmime/net/imap/IMAPMessagePart.hpp" + + +namespace vmime { +namespace net { +namespace imap { + + +IMAPMessageStructure::IMAPMessageStructure() +{ +} + + +IMAPMessageStructure::IMAPMessageStructure(const IMAPParser::body* body) +{ + m_parts.push_back(IMAPMessagePart::create(null, 0, body)); +} + + +IMAPMessageStructure::IMAPMessageStructure(shared_ptr <IMAPMessagePart> parent, const std::vector <IMAPParser::body*>& list) +{ + int number = 0; + + for (std::vector <IMAPParser::body*>::const_iterator + it = list.begin() ; it != list.end() ; ++it, ++number) + { + m_parts.push_back(IMAPMessagePart::create(parent, number, *it)); + } +} + + +shared_ptr <const messagePart> IMAPMessageStructure::getPartAt(const size_t x) const +{ + return m_parts[x]; +} + + +shared_ptr <messagePart> IMAPMessageStructure::getPartAt(const size_t x) +{ + return m_parts[x]; +} + + +size_t IMAPMessageStructure::getPartCount() const +{ + return m_parts.size(); +} + + +// static +shared_ptr <IMAPMessageStructure> IMAPMessageStructure::emptyStructure() +{ + static shared_ptr <IMAPMessageStructure> emptyStructure = make_shared <IMAPMessageStructure>(); + return emptyStructure; +} + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + diff --git a/src/vmime/net/imap/IMAPMessageStructure.hpp b/src/vmime/net/imap/IMAPMessageStructure.hpp new file mode 100644 index 00000000..44b6d6f0 --- /dev/null +++ b/src/vmime/net/imap/IMAPMessageStructure.hpp @@ -0,0 +1,75 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_IMAP_IMAPMESSAGESTRUCTURE_HPP_INCLUDED +#define VMIME_NET_IMAP_IMAPMESSAGESTRUCTURE_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include "vmime/net/message.hpp" + +#include "vmime/net/imap/IMAPParser.hpp" + + +namespace vmime { +namespace net { +namespace imap { + + +class IMAPMessagePart; + + +class VMIME_EXPORT IMAPMessageStructure : public messageStructure +{ +public: + + IMAPMessageStructure(); + IMAPMessageStructure(const IMAPParser::body* body); + IMAPMessageStructure(shared_ptr <IMAPMessagePart> parent, const std::vector <IMAPParser::body*>& list); + + shared_ptr <const messagePart> getPartAt(const size_t x) const; + shared_ptr <messagePart> getPartAt(const size_t x); + size_t getPartCount() const; + + static shared_ptr <IMAPMessageStructure> emptyStructure(); + +private: + + std::vector <shared_ptr <IMAPMessagePart> > m_parts; +}; + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + +#endif // VMIME_NET_IMAP_IMAPMESSAGESTRUCTURE_HPP_INCLUDED + diff --git a/src/vmime/net/imap/IMAPParser.hpp b/src/vmime/net/imap/IMAPParser.hpp new file mode 100644 index 00000000..8c7fcb60 --- /dev/null +++ b/src/vmime/net/imap/IMAPParser.hpp @@ -0,0 +1,5617 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_IMAP_IMAPPARSER_HPP_INCLUDED +#define VMIME_NET_IMAP_IMAPPARSER_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include "vmime/base.hpp" +#include "vmime/dateTime.hpp" +#include "vmime/charset.hpp" +#include "vmime/exception.hpp" + +#include "vmime/utility/stringUtils.hpp" +#include "vmime/utility/progressListener.hpp" + +#include "vmime/utility/encoder/b64Encoder.hpp" +#include "vmime/utility/encoder/qpEncoder.hpp" + +#include "vmime/utility/inputStreamStringAdapter.hpp" +#include "vmime/utility/outputStreamStringAdapter.hpp" + +#include "vmime/platform.hpp" + +#include "vmime/net/timeoutHandler.hpp" +#include "vmime/net/socket.hpp" + +#include "vmime/net/imap/IMAPTag.hpp" + +#include <vector> +#include <stdexcept> +#include <memory> + + +//#define DEBUG_RESPONSE 1 + + +#if DEBUG_RESPONSE +# include <iostream> +#endif + + +namespace vmime { +namespace net { +namespace imap { + + +#if DEBUG_RESPONSE + static int IMAPParserDebugResponse_level = 0; + static std::vector <string> IMAPParserDebugResponse_stack; + + class IMAPParserDebugResponse + { + public: + + IMAPParserDebugResponse(const string& name, string& line, const size_t currentPos) + : m_name(name), m_line(line), m_pos(currentPos) + { + ++IMAPParserDebugResponse_level; + IMAPParserDebugResponse_stack.push_back(name); + + for (int i = 0 ; i < IMAPParserDebugResponse_level ; ++i) + std::cout << " "; + + std::cout << "ENTER(" << m_name << "), pos=" << m_pos; + std::cout << std::endl; + + for (std::vector <string>::iterator it = IMAPParserDebugResponse_stack.begin() ; + it != IMAPParserDebugResponse_stack.end() ; ++it) + { + std::cout << "> " << *it << " "; + } + + std::cout << std::endl; + std::cout << string(m_line.begin() + (m_pos < 30 ? 0U : m_pos - 30), + m_line.begin() + std::min(m_line.length(), m_pos + 30)) << std::endl; + + for (size_t i = (m_pos < 30 ? m_pos : (m_pos - (m_pos - 30))) ; i != 0 ; --i) + std::cout << " "; + + std::cout << "^" << std::endl; + } + + ~IMAPParserDebugResponse() + { + for (int i = 0 ; i < IMAPParserDebugResponse_level ; ++i) + std::cout << " "; + + std::cout << "LEAVE(" << m_name << "), result="; + std::cout << (std::uncaught_exception() ? "FALSE" : "TRUE") << ", pos=" << m_pos; + std::cout << std::endl; + + --IMAPParserDebugResponse_level; + IMAPParserDebugResponse_stack.pop_back(); + } + + private: + + const string& m_name; + string& m_line; + size_t m_pos; + }; + + + #define DEBUG_ENTER_COMPONENT(x) \ + IMAPParserDebugResponse dbg(x, line, *currentPos) + + #define DEBUG_FOUND(x, y) \ + std::cout << "FOUND: " << x << ": " << y << std::endl; +#else + #define DEBUG_ENTER_COMPONENT(x) + #define DEBUG_FOUND(x, y) +#endif + + +class VMIME_EXPORT IMAPParser : public object +{ +public: + + IMAPParser(weak_ptr <IMAPTag> tag, weak_ptr <socket> sok, weak_ptr <timeoutHandler> _timeoutHandler) + : m_tag(tag), m_socket(sok), m_progress(NULL), m_strict(false), + m_literalHandler(NULL), m_timeoutHandler(_timeoutHandler) + { + } + + + shared_ptr <const IMAPTag> getTag() const + { + return m_tag.lock(); + } + + void setSocket(shared_ptr <socket> sok) + { + m_socket = sok; + } + + /** Set whether we operate in strict mode (this may not work + * with some servers which are not fully standard-compliant). + * + * @param strict true to operate in strict mode, or false + * to operate in default, relaxed mode + */ + void setStrict(const bool strict) + { + m_strict = strict; + } + + /** Return true if the parser operates in strict mode, or + * false otherwise. + * + * @return true if we are in strict mode, false otherwise + */ + bool isStrict() const + { + return m_strict; + } + + + + // + // literalHandler : literal content handler + // + + class component; + + class literalHandler + { + public: + + virtual ~literalHandler() { } + + + // Abstract target class + class target + { + protected: + + target(utility::progressListener* progress) : m_progress(progress) {} + target(const target&) {} + + public: + + virtual ~target() { } + + + utility::progressListener* progressListener() { return (m_progress); } + + virtual void putData(const string& chunk) = 0; + + private: + + utility::progressListener* m_progress; + }; + + + // Target: put in a string + class targetString : public target + { + public: + + targetString(utility::progressListener* progress, vmime::string& str) + : target(progress), m_string(str) { } + + const vmime::string& string() const { return (m_string); } + vmime::string& string() { return (m_string); } + + + void putData(const vmime::string& chunk) + { + m_string += chunk; + } + + private: + + vmime::string& m_string; + }; + + + // Target: redirect to an output stream + class targetStream : public target + { + public: + + targetStream(utility::progressListener* progress, utility::outputStream& stream) + : target(progress), m_stream(stream) { } + + const utility::outputStream& stream() const { return (m_stream); } + utility::outputStream& stream() { return (m_stream); } + + + void putData(const string& chunk) + { + m_stream.write(chunk.data(), chunk.length()); + } + + private: + + utility::outputStream& m_stream; + }; + + + // Called when the parser needs to know what to do with a literal + // . comp: the component in which we are at this moment + // . data: data specific to the component (may not be used) + // + // Returns : + // . == NULL to put the literal into the response + // . != NULL to redirect the literal to the specified target + + virtual target* targetFor(const component& comp, const int data) = 0; + }; + + + // + // Base class for a terminal or a non-terminal + // + + class component + { + public: + + component() { } + virtual ~component() { } + + virtual void go(IMAPParser& parser, string& line, size_t* currentPos) = 0; + + + const string makeResponseLine(const string& comp, const string& line, + const size_t pos) + { +#if DEBUG_RESPONSE + if (pos > line.length()) + std::cout << "WARNING: component::makeResponseLine(): pos > line.length()" << std::endl; +#endif + + string result(line.substr(0, pos)); + result += "[^]"; // indicates current parser position + result += line.substr(pos, line.length()); + if (!comp.empty()) result += " [" + comp + "]"; + + return (result); + } + }; + + +#define COMPONENT_ALIAS(parent, name) \ + class name : public parent \ + { \ + void go(IMAPParser& parser, string& line, size_t* currentPos) \ + { \ + DEBUG_ENTER_COMPONENT(#name); \ + parent::go(parser, line, currentPos); \ + } \ + } + + + // + // Parse one character + // + + template <char C> + class one_char : public component + { + public: + + void go(IMAPParser& /* parser */, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT(string("one_char <") + C + ">: current='" + ((*currentPos < line.length() ? line[*currentPos] : '?')) + "'"); + + const size_t pos = *currentPos; + + if (pos < line.length() && line[pos] == C) + *currentPos = pos + 1; + else + throw exceptions::invalid_response("", makeResponseLine("", line, pos)); + } + }; + + + // + // SPACE ::= <ASCII SP, space, 0x20> + // + + class SPACE : public component + { + public: + + void go(IMAPParser& /* parser */, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("SPACE"); + + size_t pos = *currentPos; + + while (pos < line.length() && (line[pos] == ' ' || line[pos] == '\t')) + ++pos; + + if (pos > *currentPos) + *currentPos = pos; + else + throw exceptions::invalid_response("", makeResponseLine("SPACE", line, pos)); + } + }; + + + // + // CR ::= <ASCII CR, carriage return, 0x0D> + // LF ::= <ASCII LF, line feed, 0x0A> + // CRLF ::= CR LF + // + + class CRLF : public component + { + public: + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("CRLF"); + + size_t pos = *currentPos; + + parser.check <SPACE>(line, &pos, true); + + if (pos + 1 < line.length() && + line[pos] == 0x0d && line[pos + 1] == 0x0a) + { + *currentPos = pos + 2; + } + else + { + throw exceptions::invalid_response("", makeResponseLine("CRLF", line, pos)); + } + } + }; + + + // + // SPACE ::= <ASCII SP, space, 0x20> + // CTL ::= <any ASCII control character and DEL, 0x00 - 0x1f, 0x7f> + // CHAR ::= <any 7-bit US-ASCII character except NUL, 0x01 - 0x7f> + // ATOM_CHAR ::= <any CHAR except atom_specials> + // atom_specials ::= "(" / ")" / "{" / SPACE / CTL / list_wildcards / quoted_specials + // list_wildcards ::= "%" / "*" + // quoted_specials ::= <"> / "\" + // + // tag ::= 1*<any ATOM_CHAR except "+"> (named "xtag") + // + + class xtag : public component + { + public: + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("tag"); + + size_t pos = *currentPos; + + bool end = false; + + string tagString; + tagString.reserve(10); + + while (!end && pos < line.length()) + { + const unsigned char c = line[pos]; + + switch (c) + { + case '+': + case '(': + case ')': + case '{': + case 0x20: // SPACE + case '%': // list_wildcards + case '*': // list_wildcards + case '"': // quoted_specials + case '\\': // quoted_specials + + end = true; + break; + + default: + + if (c <= 0x1f || c >= 0x7f) + end = true; + else + { + tagString += c; + ++pos; + } + + break; + } + } + + if (tagString == string(*parser.getTag())) + { + *currentPos = pos; + } + else + { + // Invalid tag + throw exceptions::invalid_response("", makeResponseLine("tag", line, pos)); + } + } + }; + + + // + // digit ::= "0" / digit_nz + // digit_nz ::= "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" + // + // number ::= 1*digit + // ;; Unsigned 32-bit integer + // ;; (0 <= n < 4,294,967,296) + // + + class number : public component + { + public: + + number(const bool nonZero = false) + : m_nonZero(nonZero), m_value(0) + { + } + + void go(IMAPParser& /* parser */, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("number"); + + size_t pos = *currentPos; + + bool valid = true; + unsigned int val = 0; + + while (valid && pos < line.length()) + { + const char c = line[pos]; + + if (c >= '0' && c <= '9') + { + val = (val * 10) + (c - '0'); + ++pos; + } + else + { + valid = false; + } + } + + // Check for non-null length (and for non-zero number) + if (!(m_nonZero && val == 0) && pos != *currentPos) + { + m_value = val; + *currentPos = pos; + } + else + { + throw exceptions::invalid_response("", makeResponseLine("number", line, pos)); + } + } + + private: + + const bool m_nonZero; + unsigned long m_value; + + public: + + unsigned long value() const { return (m_value); } + }; + + + // nz_number ::= digit_nz *digit + // ;; Non-zero unsigned 32-bit integer + // ;; (0 < n < 4,294,967,296) + // + + class nz_number : public number + { + public: + + nz_number() : number(true) + { + } + }; + + + // + // text ::= 1*TEXT_CHAR + // + // CHAR ::= <any 7-bit US-ASCII character except NUL, 0x01 - 0x7f> + // TEXT_CHAR ::= <any CHAR except CR and LF> + // + + class text : public component + { + public: + + text(bool allow8bits = false, const char except = 0) + : m_allow8bits(allow8bits), m_except(except) + { + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("text"); + + size_t pos = *currentPos; + size_t len = 0; + + if (m_allow8bits || !parser.isStrict()) + { + const unsigned char except = m_except; + + for (bool end = false ; !end && pos < line.length() ; ) + { + const unsigned char c = line[pos]; + + if (c == 0x00 || c == 0x0d || c == 0x0a || c == except) + { + end = true; + } + else + { + ++pos; + ++len; + } + } + } + else + { + const unsigned char except = m_except; + + for (bool end = false ; !end && pos < line.length() ; ) + { + const unsigned char c = line[pos]; + + if (c < 0x01 || c > 0x7f || c == 0x0d || c == 0x0a || c == except) + { + end = true; + } + else + { + ++pos; + ++len; + } + } + } + + if (len != 0) + { + m_value.resize(len); + std::copy(line.begin() + *currentPos, line.begin() + pos, m_value.begin()); + + *currentPos = pos; + } + else + { + throw exceptions::invalid_response("", makeResponseLine("text", line, pos)); + } + } + + private: + + string m_value; + const bool m_allow8bits; + const char m_except; + + public: + + const string& value() const { return (m_value); } + }; + + + class text8 : public text + { + public: + + text8() : text(true) + { + } + }; + + + template <char C> + class text_except : public text + { + public: + + text_except() : text(false, C) + { + } + }; + + + template <char C> + class text8_except : public text + { + public: + + text8_except() : text(true, C) + { + } + }; + + + // + // QUOTED_CHAR ::= <any TEXT_CHAR except quoted_specials> / "\" quoted_specials + // quoted_specials ::= <"> / "\" + // TEXT_CHAR ::= <any CHAR except CR and LF> + // CHAR ::= <any 7-bit US-ASCII character except NUL, 0x01 - 0x7f> + // + + class QUOTED_CHAR : public component + { + public: + + void go(IMAPParser& /* parser */, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("quoted_char"); + + size_t pos = *currentPos; + + const unsigned char c = static_cast <unsigned char>(pos < line.length() ? line[pos] : 0); + + if (c >= 0x01 && c <= 0x7f && // 0x01 - 0x7f + c != '"' && c != '\\' && // quoted_specials + c != '\r' && c != '\n') // CR and LF + { + m_value = c; + *currentPos = pos + 1; + } + else if (c == '\\' && pos + 1 < line.length() && + (line[pos + 1] == '"' || line[pos + 1] == '\\')) + { + m_value = line[pos + 1]; + *currentPos = pos + 2; + } + else + { + throw exceptions::invalid_response("", makeResponseLine("QUOTED_CHAR", line, pos)); + } + } + + private: + + char m_value; + + public: + + char value() const { return (m_value); } + }; + + + // + // quoted ::= <"> *QUOTED_CHAR <"> + // QUOTED_CHAR ::= <any TEXT_CHAR except quoted_specials> / "\" quoted_specials + // quoted_specials ::= <"> / "\" + // TEXT_CHAR ::= <any CHAR except CR and LF> + // CHAR ::= <any 7-bit US-ASCII character except NUL, 0x01 - 0x7f> + // + + class quoted_text : public component + { + public: + + void go(IMAPParser& /* parser */, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("quoted_text"); + + size_t pos = *currentPos; + size_t len = 0; + bool valid = false; + + m_value.reserve(line.length() - pos); + + for (bool end = false, quoted = false ; !end && pos < line.length() ; ) + { + const unsigned char c = line[pos]; + + if (quoted) + { + if (c == '"' || c == '\\') + m_value += c; + else + { + m_value += '\\'; + m_value += c; + } + + quoted = false; + + ++pos; + ++len; + } + else + { + if (c == '\\') + { + quoted = true; + + ++pos; + ++len; + } + else if (c == '"') + { + valid = true; + end = true; + } + else if (c >= 0x01 && c <= 0x7f && // CHAR + c != 0x0a && c != 0x0d) // CR and LF + { + m_value += c; + + ++pos; + ++len; + } + else + { + valid = false; + end = true; + } + } + } + + if (valid) + { + *currentPos = pos; + } + else + { + throw exceptions::invalid_response("", makeResponseLine("quoted_text", line, pos)); + } + } + + private: + + string m_value; + + public: + + const string& value() const { return (m_value); } + }; + + + // + // nil ::= "NIL" + // + + class NIL : public component + { + public: + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("NIL"); + + size_t pos = *currentPos; + + parser.checkWithArg <special_atom>(line, &pos, "nil"); + + *currentPos = pos; + } + }; + + + // + // string ::= quoted / literal ----> named 'xstring' + // + // nil ::= "NIL" + // quoted ::= <"> *QUOTED_CHAR <"> + // QUOTED_CHAR ::= <any TEXT_CHAR except quoted_specials> / "\" quoted_specials + // quoted_specials ::= <"> / "\" + // TEXT_CHAR ::= <any CHAR except CR and LF> + // CHAR ::= <any 7-bit US-ASCII character except NUL, 0x01 - 0x7f> + // literal ::= "{" number "}" CRLF *CHAR8 + // ;; Number represents the number of CHAR8 octets + // CHAR8 ::= <any 8-bit octet except NUL, 0x01 - 0xff> + // + + class xstring : public component + { + public: + + xstring(const bool canBeNIL = false, component* comp = NULL, const int data = 0) + : m_canBeNIL(canBeNIL), m_component(comp), m_data(data) + { + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("string"); + + size_t pos = *currentPos; + + if (m_canBeNIL && + parser.checkWithArg <special_atom>(line, &pos, "nil", true)) + { + // NIL + } + else + { + pos = *currentPos; + + // quoted ::= <"> *QUOTED_CHAR <"> + if (parser.check <one_char <'"'> >(line, &pos, true)) + { + std::auto_ptr <quoted_text> text(parser.get <quoted_text>(line, &pos)); + parser.check <one_char <'"'> >(line, &pos); + + if (parser.m_literalHandler != NULL) + { + literalHandler::target* target = + parser.m_literalHandler->targetFor(*m_component, m_data); + + if (target != NULL) + { + m_value = "[literal-handler]"; + + const size_t length = text->value().length(); + utility::progressListener* progress = target->progressListener(); + + if (progress) + { + progress->start(length); + } + + target->putData(text->value()); + + if (progress) + { + progress->progress(length, length); + progress->stop(length); + } + + delete (target); + } + else + { + m_value = text->value(); + } + } + else + { + m_value = text->value(); + } + + DEBUG_FOUND("string[quoted]", "<length=" << m_value.length() << ", value='" << m_value << "'>"); + } + // literal ::= "{" number "}" CRLF *CHAR8 + else + { + parser.check <one_char <'{'> >(line, &pos); + + number* num = parser.get <number>(line, &pos); + + const size_t length = num->value(); + delete (num); + + parser.check <one_char <'}'> >(line, &pos); + + parser.check <CRLF>(line, &pos); + + + if (parser.m_literalHandler != NULL) + { + literalHandler::target* target = + parser.m_literalHandler->targetFor(*m_component, m_data); + + if (target != NULL) + { + m_value = "[literal-handler]"; + + parser.m_progress = target->progressListener(); + parser.readLiteral(*target, length); + parser.m_progress = NULL; + + delete (target); + } + else + { + literalHandler::targetString target(NULL, m_value); + parser.readLiteral(target, length); + } + } + else + { + literalHandler::targetString target(NULL, m_value); + parser.readLiteral(target, length); + } + + line += parser.readLine(); + + DEBUG_FOUND("string[literal]", "<length=" << length << ", value='" << m_value << "'>"); + } + } + + *currentPos = pos; + } + + private: + + bool m_canBeNIL; + string m_value; + + component* m_component; + const int m_data; + + public: + + const string& value() const { return (m_value); } + void setValue(const string& val) { m_value = val; } + }; + + + // + // nstring ::= string / nil + // + + class nstring : public xstring + { + public: + + nstring(component* comp = NULL, const int data = 0) + : xstring(true, comp, data) + { + } + }; + + + // + // astring ::= atom / string + // + + class astring : public component + { + public: + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("astring"); + + size_t pos = *currentPos; + + xstring* str = NULL; + + if ((str = parser.get <xstring>(line, &pos, true))) + { + m_value = str->value(); + delete (str); + } + else + { + atom* at = parser.get <atom>(line, &pos); + m_value = at->value(); + delete (at); + } + + *currentPos = pos; + } + + private: + + string m_value; + + public: + + const string& value() const { return (m_value); } + }; + + + // + // atom ::= 1*ATOM_CHAR + // + // ATOM_CHAR ::= <any CHAR except atom_specials> + // atom_specials ::= "(" / ")" / "{" / SPACE / CTL / 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> + // list_wildcards ::= "%" / "*" + // quoted_specials ::= <"> / "\" + // SPACE ::= <ASCII SP, space, 0x20> + // + + class atom : public component + { + public: + + void go(IMAPParser& /* parser */, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("atom"); + + size_t pos = *currentPos; + size_t len = 0; + + for (bool end = false ; !end && pos < line.length() ; ) + { + const unsigned char c = line[pos]; + + switch (c) + { + case '(': + case ')': + case '{': + case 0x20: // SPACE + case '%': // list_wildcards + case '*': // list_wildcards + case '"': // quoted_specials + case '\\': // quoted_specials + + case '[': + case ']': // for "special_atom" + + end = true; + break; + + default: + + if (c <= 0x1f || c >= 0x7f) + end = true; + else + { + ++pos; + ++len; + } + } + } + + if (len != 0) + { + m_value.resize(len); + std::copy(line.begin() + *currentPos, line.begin() + pos, m_value.begin()); + + *currentPos = pos; + } + else + { + throw exceptions::invalid_response("", makeResponseLine("atom", line, pos)); + } + } + + private: + + string m_value; + + public: + + const string& value() const { return (m_value); } + }; + + + // + // special atom (eg. "CAPABILITY", "FLAGS", "STATUS"...) + // + // " Except as noted otherwise, all alphabetic characters are case- + // insensitive. The use of upper or lower case characters to define + // token strings is for editorial clarity only. Implementations MUST + // accept these strings in a case-insensitive fashion. " + // + + class special_atom : public atom + { + public: + + special_atom(const char* str) + : m_string(str) // 'string' must be in lower-case + { + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT(string("special_atom(") + m_string + ")"); + + size_t pos = *currentPos; + + atom::go(parser, line, &pos); + + const char* cmp = value().c_str(); + const char* with = m_string; + + bool ok = true; + + while (ok && *cmp && *with) + { + ok = (std::tolower(*cmp, std::locale()) == *with); + + ++cmp; + ++with; + } + + if (!ok || *cmp || *with) + { + throw exceptions::invalid_response("", makeResponseLine(string("special_atom <") + m_string + ">", line, pos)); + } + else + { + *currentPos = pos; + } + } + + private: + + const char* m_string; + }; + + + // + // text_mime2 ::= "=?" <charset> "?" <encoding> "?" <encoded-text> "?=" + // ;; Syntax defined in [MIME-HDRS] + // + + class text_mime2 : public component + { + public: + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("text_mime2"); + + size_t pos = *currentPos; + + atom* theCharset = NULL, *theEncoding = NULL; + text* theText = NULL; + + try + { + parser.check <one_char <'='> >(line, &pos); + parser.check <one_char <'?'> >(line, &pos); + + theCharset = parser.get <atom>(line, &pos); + + parser.check <one_char <'?'> >(line, &pos); + + theEncoding = parser.get <atom>(line, &pos); + + parser.check <one_char <'?'> >(line, &pos); + + theText = parser.get <text8_except <'?'> >(line, &pos); + + parser.check <one_char <'?'> >(line, &pos); + parser.check <one_char <'='> >(line, &pos); + } + catch (std::exception&) + { + delete (theCharset); + delete (theEncoding); + delete (theText); + + throw; + } + + m_charset = theCharset->value(); + delete (theCharset); + + // Decode text + utility::encoder::encoder* theEncoder = NULL; + + if (theEncoding->value()[0] == 'q' || theEncoding->value()[0] == 'Q') + { + // Quoted-printable + theEncoder = new utility::encoder::qpEncoder(); + theEncoder->getProperties()["rfc2047"] = true; + } + else if (theEncoding->value()[0] == 'b' || theEncoding->value()[0] == 'B') + { + // Base64 + theEncoder = new utility::encoder::b64Encoder(); + } + + if (theEncoder) + { + utility::inputStreamStringAdapter in(theText->value()); + utility::outputStreamStringAdapter out(m_value); + + theEncoder->decode(in, out); + delete (theEncoder); + } + // No decoder available + else + { + m_value = theText->value(); + } + + delete (theEncoding); + delete (theText); + + *currentPos = pos; + } + + private: + + vmime::charset m_charset; + string m_value; + + public: + + const vmime::charset& charset() const { return (m_charset); } + const string& value() const { return (m_value); } + }; + + + // seq-number = nz-number / "*" + // ; message sequence number (COPY, FETCH, STORE + // ; commands) or unique identifier (UID COPY, + // ; UID FETCH, UID STORE commands). + + class seq_number : public component + { + public: + + seq_number() + : m_number(NULL), m_star(false) + { + } + + ~seq_number() + { + delete m_number; + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("seq_number"); + + size_t pos = *currentPos; + + if (parser.check <one_char <'*'> >(line, &pos, true)) + { + m_star = true; + m_number = NULL; + } + else + { + m_star = false; + m_number = parser.get <IMAPParser::number>(line, &pos); + } + + *currentPos = pos; + } + + private: + + IMAPParser::number* m_number; + bool m_star; + + public: + + const IMAPParser::number* number() const { return m_number; } + bool star() const { return m_star; } + }; + + + // seq-range = seq-number ":" seq-number + // ; two seq-number values and all values between + // ; these two regardless of order. + // ; Example: 2:4 and 4:2 are equivalent and indicate + // ; values 2, 3, and 4. + + class seq_range : public component + { + public: + + seq_range() + : m_first(NULL), m_last(NULL) + { + } + + ~seq_range() + { + delete m_first; + delete m_last; + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("seq_range"); + + size_t pos = *currentPos; + + m_first = parser.get <seq_number>(line, &pos); + + parser.check <one_char <'*'> >(line, &pos); + + m_last = parser.get <seq_number>(line, &pos); + + *currentPos = pos; + } + + private: + + IMAPParser::seq_number* m_first; + IMAPParser::seq_number* m_last; + + public: + + const IMAPParser::seq_number* first() const { return m_first; } + const IMAPParser::seq_number* last() const { return m_last; } + }; + + + // sequence-set = (seq-number / seq-range) *("," sequence-set) + // ; set of seq-number values, regardless of order. + // ; Servers MAY coalesce overlaps and/or execute the + // ; sequence in any order. + // ; Example: a message sequence number set of + // ; 2,4:7,9,12:* for a mailbox with 15 messages is + // ; equivalent to 2,4,5,6,7,9,12,13,14,15 + + class sequence_set : public component + { + public: + + sequence_set() + : m_number(NULL), m_range(NULL), m_nextSet(NULL) + { + } + + ~sequence_set() + { + delete m_number; + delete m_range; + delete m_nextSet; + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("sequence_set"); + + size_t pos = *currentPos; + + if ((m_range = parser.get <IMAPParser::seq_range>(line, &pos, true)) == NULL) + m_number = parser.get <IMAPParser::seq_number>(line, &pos); + + if (parser.check <one_char <','> >(line, &pos, true)) + m_nextSet = parser.get <sequence_set>(line, &pos); + + *currentPos = pos; + } + + private: + + IMAPParser::seq_number* m_number; + IMAPParser::seq_range* m_range; + IMAPParser::sequence_set* m_nextSet; + + public: + + const IMAPParser::seq_number* seq_number() const { return m_number; } + const IMAPParser::seq_range* seq_range() const { return m_range; } + const IMAPParser::sequence_set* next_sequence_set() const { return m_nextSet; } + }; + + + // mod-sequence-value = 1*DIGIT + // ;; Positive unsigned 64-bit integer + // ;; (mod-sequence) + // ;; (1 <= n < 18,446,744,073,709,551,615) + + class mod_sequence_value : public component + { + public: + + mod_sequence_value() + : m_value(0) + { + } + + void go(IMAPParser& /* parser */, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("mod_sequence_value"); + + size_t pos = *currentPos; + + bool valid = true; + vmime_uint64 val = 0; + + while (valid && pos < line.length()) + { + const char c = line[pos]; + + if (c >= '0' && c <= '9') + { + val = (val * 10) + (c - '0'); + ++pos; + } + else + { + valid = false; + } + } + + m_value = val; + + *currentPos = pos; + } + + private: + + vmime_uint64 m_value; + + public: + + vmime_uint64 value() const { return m_value; } + }; + + + // + // flag ::= "\Answered" / "\Flagged" / "\Deleted" / + // "\Seen" / "\Draft" / flag_keyword / flag_extension + // + // flag_extension ::= "\" atom + // ;; Future expansion. Client implementations + // ;; MUST accept flag_extension flags. Server + // ;; implementations MUST NOT generate + // ;; flag_extension flags except as defined by + // ;; future standard or standards-track + // ;; revisions of this specification. + // + // flag_keyword ::= atom + // + + class flag : public component + { + public: + + flag() + : m_type(UNKNOWN), m_flag_keyword(NULL) + { + } + + ~flag() + { + delete (m_flag_keyword); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("flag_keyword"); + + size_t pos = *currentPos; + + if (parser.check <one_char <'\\'> >(line, &pos, true)) + { + if (parser.check <one_char <'*'> >(line, &pos, true)) + { + m_type = STAR; + } + else + { + atom* at = parser.get <atom>(line, &pos); + const string name = utility::stringUtils::toLower(at->value()); + delete (at); + + if (name == "answered") + m_type = ANSWERED; + else if (name == "flagged") + m_type = FLAGGED; + else if (name == "deleted") + m_type = DELETED; + else if (name == "seen") + m_type = SEEN; + else if (name == "draft") + m_type = DRAFT; + else + { + m_type = UNKNOWN; + m_name = name; + } + } + } + else + { + m_type = KEYWORD_OR_EXTENSION; + m_flag_keyword = parser.get <atom>(line, &pos); + } + + *currentPos = pos; + } + + + enum Type + { + UNKNOWN, + ANSWERED, + FLAGGED, + DELETED, + SEEN, + DRAFT, + KEYWORD_OR_EXTENSION, + STAR // * = custom flags allowed + }; + + private: + + Type m_type; + string m_name; + + IMAPParser::atom* m_flag_keyword; + + public: + + Type type() const { return (m_type); } + const string& name() const { return (m_name); } + + const IMAPParser::atom* flag_keyword() const { return (m_flag_keyword); } + }; + + + // + // flag_list ::= "(" #flag ")" + // + + class flag_list : public component + { + public: + + ~flag_list() + { + for (std::vector <flag*>::iterator it = m_flags.begin() ; + it != m_flags.end() ; ++it) + { + delete (*it); + } + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("flag_list"); + + size_t pos = *currentPos; + + parser.check <one_char <'('> >(line, &pos); + + while (!parser.check <one_char <')'> >(line, &pos, true)) + { + m_flags.push_back(parser.get <flag>(line, &pos)); + parser.check <SPACE>(line, &pos, true); + } + + *currentPos = pos; + } + + private: + + std::vector <flag*> m_flags; + + public: + + const std::vector <flag*>& flags() const { return (m_flags); } + }; + + + // + // mailbox ::= "INBOX" / astring + // ;; INBOX is case-insensitive. All case variants of + // ;; INBOX (e.g. "iNbOx") MUST be interpreted as INBOX + // ;; not as an astring. Refer to section 5.1 for + // ;; further semantic details of mailbox names. + // + + class mailbox : public component + { + public: + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("mailbox"); + + size_t pos = *currentPos; + + if (parser.checkWithArg <special_atom>(line, &pos, "inbox", true)) + { + m_type = INBOX; + m_name = "INBOX"; + } + else + { + m_type = OTHER; + + astring* astr = parser.get <astring>(line, &pos); + m_name = astr->value(); + delete (astr); + } + + *currentPos = pos; + } + + + enum Type + { + INBOX, + OTHER + }; + + private: + + Type m_type; + string m_name; + + public: + + Type type() const { return (m_type); } + const string& name() const { return (m_name); } + }; + + + // + // mailbox_flag := "\Marked" / "\Noinferiors" / + // "\Noselect" / "\Unmarked" / flag_extension + // + + class mailbox_flag : public component + { + public: + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("mailbox_flag"); + + size_t pos = *currentPos; + + if (parser.check <one_char <'\\'> >(line, &pos, true)) + { + atom* at = parser.get <atom>(line, &pos); + const string name = utility::stringUtils::toLower(at->value()); + delete (at); + + if (name == "marked") + m_type = MARKED; + else if (name == "noinferiors") + m_type = NOINFERIORS; + else if (name == "noselect") + m_type = NOSELECT; + else if (name == "unmarked") + m_type = UNMARKED; + else + { + m_type = UNKNOWN; + m_name = "\\" + name; + } + } + else + { + atom* at = parser.get <atom>(line, &pos); + const string name = utility::stringUtils::toLower(at->value()); + delete (at); + + m_type = UNKNOWN; + m_name = name; + } + + *currentPos = pos; + } + + + enum Type + { + UNKNOWN, + MARKED, + NOINFERIORS, + NOSELECT, + UNMARKED + }; + + private: + + Type m_type; + string m_name; + + public: + + Type type() const { return (m_type); } + const string& name() const { return (m_name); } + }; + + + // + // mailbox_flag_list ::= "(" #(mailbox_flag) ")" + // + + class mailbox_flag_list : public component + { + public: + + ~mailbox_flag_list() + { + for (std::vector <mailbox_flag*>::iterator it = m_flags.begin() ; + it != m_flags.end() ; ++it) + { + delete (*it); + } + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("mailbox_flag_list"); + + size_t pos = *currentPos; + + parser.check <one_char <'('> >(line, &pos); + + while (!parser.check <one_char <')'> >(line, &pos, true)) + { + m_flags.push_back(parser.get <mailbox_flag>(line, &pos)); + parser.check <SPACE>(line, &pos, true); + } + + *currentPos = pos; + } + + private: + + std::vector <mailbox_flag*> m_flags; + + public: + + const std::vector <mailbox_flag*>& flags() const { return (m_flags); } + }; + + + // + // mailbox_list ::= mailbox_flag_list SPACE + // (<"> QUOTED_CHAR <"> / nil) SPACE mailbox + // + + class mailbox_list : public component + { + public: + + mailbox_list() + : m_mailbox_flag_list(NULL), + m_mailbox(NULL), m_quoted_char('\0') + { + } + + ~mailbox_list() + { + delete (m_mailbox_flag_list); + delete (m_mailbox); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("mailbox_list"); + + size_t pos = *currentPos; + + m_mailbox_flag_list = parser.get <IMAPParser::mailbox_flag_list>(line, &pos); + + parser.check <SPACE>(line, &pos); + + if (!parser.check <NIL>(line, &pos, true)) + { + parser.check <one_char <'"'> >(line, &pos); + + QUOTED_CHAR* qc = parser.get <QUOTED_CHAR>(line, &pos); + m_quoted_char = qc->value(); + delete (qc); + + parser.check <one_char <'"'> >(line, &pos); + } + + parser.check <SPACE>(line, &pos); + + m_mailbox = parser.get <IMAPParser::mailbox>(line, &pos); + + *currentPos = pos; + } + + private: + + IMAPParser::mailbox_flag_list* m_mailbox_flag_list; + IMAPParser::mailbox* m_mailbox; + char m_quoted_char; + + public: + + const IMAPParser::mailbox_flag_list* mailbox_flag_list() const { return (m_mailbox_flag_list); } + const IMAPParser::mailbox* mailbox() const { return (m_mailbox); } + char quoted_char() const { return (m_quoted_char); } + }; + + + // + // auth_type ::= atom + // ;; Defined by [IMAP-AUTH] + // + + class auth_type : public component + { + public: + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("auth_type"); + + atom* at = parser.get <atom>(line, currentPos); + m_name = utility::stringUtils::toLower(at->value()); + delete (at); + + if (m_name == "kerberos_v4") + m_type = KERBEROS_V4; + else if (m_name == "gssapi") + m_type = GSSAPI; + else if (m_name == "skey") + m_type = SKEY; + else + m_type = UNKNOWN; + } + + + enum Type + { + UNKNOWN, + + // RFC 1731 - IMAP4 Authentication Mechanisms + KERBEROS_V4, + GSSAPI, + SKEY + }; + + private: + + Type m_type; + string m_name; + + public: + + Type type() const { return (m_type); } + const string name() const { return (m_name); } + }; + + + // + // status-att-val = ("MESSAGES" SP number) / + // ("RECENT" SP number) / + // ("UIDNEXT" SP nz-number) / + // ("UIDVALIDITY" SP nz-number) / + // ("UNSEEN" SP number) + // + // IMAP Extension for Conditional STORE (RFC-4551): + // + // status-att-val =/ "HIGHESTMODSEQ" SP mod-sequence-valzer + // ;; extends non-terminal defined in [IMAPABNF]. + // ;; Value 0 denotes that the mailbox doesn't + // ;; support persistent mod-sequences + // + + class status_att_val : public component + { + public: + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("status_att"); + + size_t pos = *currentPos; + + // "HIGHESTMODSEQ" SP mod-sequence-valzer + if (parser.checkWithArg <special_atom>(line, &pos, "highestmodseq", true)) + { + m_type = HIGHESTMODSEQ; + + parser.check <SPACE>(line, &pos); + m_value = parser.get <IMAPParser::mod_sequence_value>(line, &pos); + } + else + { + if (parser.checkWithArg <special_atom>(line, &pos, "messages", true)) + { + m_type = MESSAGES; + } + else if (parser.checkWithArg <special_atom>(line, &pos, "recent", true)) + { + m_type = RECENT; + } + else if (parser.checkWithArg <special_atom>(line, &pos, "uidnext", true)) + { + m_type = UIDNEXT; + } + else if (parser.checkWithArg <special_atom>(line, &pos, "uidvalidity", true)) + { + m_type = UIDVALIDITY; + } + else + { + parser.checkWithArg <special_atom>(line, &pos, "unseen"); + m_type = UNSEEN; + } + + parser.check <SPACE>(line, &pos); + m_value = parser.get <IMAPParser::number>(line, &pos); + } + + *currentPos = pos; + } + + + enum Type + { + // Extensions + HIGHESTMODSEQ, + + // Standard IMAP + MESSAGES, + RECENT, + UIDNEXT, + UIDVALIDITY, + UNSEEN + }; + + private: + + Type m_type; + IMAPParser::component* m_value; + + public: + + Type type() const { return (m_type); } + + const IMAPParser::number* value_as_number() const + { + return dynamic_cast <IMAPParser::number *>(m_value); + } + + const IMAPParser::mod_sequence_value* value_as_mod_sequence_value() const + { + return dynamic_cast <IMAPParser::mod_sequence_value *>(m_value); + } + }; + + + // status-att-list = status-att-val *(SP status-att-val) + + class status_att_list : public component + { + public: + + ~status_att_list() + { + for (std::vector <status_att_val*>::iterator it = m_values.begin() ; + it != m_values.end() ; ++it) + { + delete *it; + } + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("status_att_list"); + + size_t pos = *currentPos; + + m_values.push_back(parser.get <IMAPParser::status_att_val>(line, &pos)); + + while (parser.check <SPACE>(line, &pos, true)) + m_values.push_back(parser.get <IMAPParser::status_att_val>(line, &pos)); + + *currentPos = pos; + } + + private: + + std::vector <status_att_val*> m_values; + + public: + + const std::vector <status_att_val*>& values() const { return m_values; } + }; + + + // + // capability ::= "AUTH=" auth_type / atom + // ;; New capabilities MUST begin with "X" or be + // ;; registered with IANA as standard or standards-track + // + + class capability : public component + { + public: + + capability() + : m_auth_type(NULL), m_atom(NULL) + { + } + + ~capability() + { + delete (m_auth_type); + delete (m_atom); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("capability"); + + size_t pos = *currentPos; + + class atom* at = parser.get <IMAPParser::atom>(line, &pos); + + string value = at->value(); + const char* str = value.c_str(); + + if ((str[0] == 'a' || str[0] == 'A') && + (str[1] == 'u' || str[1] == 'U') && + (str[2] == 't' || str[2] == 'T') && + (str[3] == 'h' || str[3] == 'H') && + (str[4] == '=')) + { + size_t pos = 5; + m_auth_type = parser.get <IMAPParser::auth_type>(value, &pos); + delete (at); + } + else + { + m_atom = at; + } + + *currentPos = pos; + } + + private: + + IMAPParser::auth_type* m_auth_type; + IMAPParser::atom* m_atom; + + public: + + const IMAPParser::auth_type* auth_type() const { return (m_auth_type); } + const IMAPParser::atom* atom() const { return (m_atom); } + }; + + + // + // capability_data ::= "CAPABILITY" SPACE [1#capability SPACE] "IMAP4rev1" + // [SPACE 1#capability] + // ;; IMAP4rev1 servers which offer RFC 1730 + // ;; compatibility MUST list "IMAP4" as the first + // ;; capability. + // + + class capability_data : public component + { + public: + + ~capability_data() + { + for (std::vector <capability*>::iterator it = m_capabilities.begin() ; + it != m_capabilities.end() ; ++it) + { + delete (*it); + } + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("capability_data"); + + size_t pos = *currentPos; + + parser.checkWithArg <special_atom>(line, &pos, "capability"); + + while (parser.check <SPACE>(line, &pos, true)) + { + capability* cap; + + if (parser.isStrict() || m_capabilities.empty()) + cap = parser.get <capability>(line, &pos); + else + cap = parser.get <capability>(line, &pos, /* noThrow */ true); // allow SPACE at end of line (Apple iCloud IMAP server) + + if (cap == NULL) break; + + m_capabilities.push_back(cap); + } + + *currentPos = pos; + } + + private: + + std::vector <capability*> m_capabilities; + + public: + + const std::vector <capability*>& capabilities() const { return (m_capabilities); } + }; + + + // + // date_day_fixed ::= (SPACE digit) / 2digit + // ;; Fixed-format version of date_day + // + // date_month ::= "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" / + // "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec" + // + // date_year ::= 4digit + // + // time ::= 2digit ":" 2digit ":" 2digit + // ;; Hours minutes seconds + // + // zone ::= ("+" / "-") 4digit + // ;; Signed four-digit value of hhmm representing + // ;; hours and minutes west of Greenwich (that is, + // ;; (the amount that the given time differs from + // ;; Universal Time). Subtracting the timezone + // ;; from the given time will give the UT form. + // ;; The Universal Time zone is "+0000". + // + // date_time ::= <"> date_day_fixed "-" date_month "-" date_year + // SPACE time SPACE zone <"> + // + + class date_time : public component + { + public: + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("date_time"); + + size_t pos = *currentPos; + + // <"> date_day_fixed "-" date_month "-" date_year + parser.check <one_char <'"'> >(line, &pos); + parser.check <SPACE>(line, &pos, true); + + std::auto_ptr <number> nd(parser.get <number>(line, &pos)); + + parser.check <one_char <'-'> >(line, &pos); + + std::auto_ptr <atom> amo(parser.get <atom>(line, &pos)); + + parser.check <one_char <'-'> >(line, &pos); + + std::auto_ptr <number> ny(parser.get <number>(line, &pos)); + + parser.check <SPACE>(line, &pos, true); + + // 2digit ":" 2digit ":" 2digit + std::auto_ptr <number> nh(parser.get <number>(line, &pos)); + + parser.check <one_char <':'> >(line, &pos); + + std::auto_ptr <number> nmi(parser.get <number>(line, &pos)); + + parser.check <one_char <':'> >(line, &pos); + + std::auto_ptr <number> ns(parser.get <number>(line, &pos)); + + parser.check <SPACE>(line, &pos, true); + + // ("+" / "-") 4digit + int sign = 1; + + if (!(parser.check <one_char <'+'> >(line, &pos, true))) + parser.check <one_char <'-'> >(line, &pos); + + std::auto_ptr <number> nz(parser.get <number>(line, &pos)); + + parser.check <one_char <'"'> >(line, &pos); + + + m_datetime.setHour(static_cast <int>(std::min(std::max(nh->value(), 0ul), 23ul))); + m_datetime.setMinute(static_cast <int>(std::min(std::max(nmi->value(), 0ul), 59ul))); + m_datetime.setSecond(static_cast <int>(std::min(std::max(ns->value(), 0ul), 59ul))); + + const int zone = static_cast <int>(nz->value()); + const int zh = zone / 100; // hour offset + const int zm = zone % 100; // minute offset + + m_datetime.setZone(((zh * 60) + zm) * sign); + + m_datetime.setDay(static_cast <int>(std::min(std::max(nd->value(), 1ul), 31ul))); + m_datetime.setYear(static_cast <int>(ny->value())); + + const string month(utility::stringUtils::toLower(amo->value())); + int mon = vmime::datetime::JANUARY; + + if (month.length() >= 3) + { + switch (month[0]) + { + case 'j': + { + switch (month[1]) + { + case 'a': mon = vmime::datetime::JANUARY; break; + case 'u': + { + switch (month[2]) + { + case 'n': mon = vmime::datetime::JUNE; break; + default: mon = vmime::datetime::JULY; break; + } + + break; + } + + } + + break; + } + case 'f': mon = vmime::datetime::FEBRUARY; break; + case 'm': + { + switch (month[2]) + { + case 'r': mon = vmime::datetime::MARCH; break; + default: mon = vmime::datetime::MAY; break; + } + + break; + } + case 'a': + { + switch (month[1]) + { + case 'p': mon = vmime::datetime::APRIL; break; + default: mon = vmime::datetime::AUGUST; break; + } + + break; + } + case 's': mon = vmime::datetime::SEPTEMBER; break; + case 'o': mon = vmime::datetime::OCTOBER; break; + case 'n': mon = vmime::datetime::NOVEMBER; break; + case 'd': mon = vmime::datetime::DECEMBER; break; + } + } + + m_datetime.setMonth(mon); + + *currentPos = pos; + } + + private: + + vmime::datetime m_datetime; + }; + + + // + // header_fld_name ::= astring + // + + typedef astring header_fld_name; + + + // + // header_list ::= "(" 1#header_fld_name ")" + // + + class header_list : public component + { + public: + + ~header_list() + { + for (std::vector <header_fld_name*>::iterator it = m_fld_names.begin() ; + it != m_fld_names.end() ; ++it) + { + delete (*it); + } + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("header_list"); + + size_t pos = *currentPos; + + parser.check <one_char <'('> >(line, &pos); + + while (!parser.check <one_char <')'> >(line, &pos, true)) + { + m_fld_names.push_back(parser.get <header_fld_name>(line, &pos)); + parser.check <SPACE>(line, &pos, true); + } + + *currentPos = pos; + } + + private: + + std::vector <header_fld_name*> m_fld_names; + + public: + + const std::vector <header_fld_name*>& fld_names() const { return (m_fld_names); } + }; + + + // + // body_extension ::= nstring / number / "(" 1#body_extension ")" + // ;; Future expansion. Client implementations + // ;; MUST accept body_extension fields. Server + // ;; implementations MUST NOT generate + // ;; body_extension fields except as defined by + // ;; future standard or standards-track + // ;; revisions of this specification. + // + + class body_extension : public component + { + public: + + body_extension() + : m_nstring(NULL), m_number(NULL) + { + } + + ~body_extension() + { + delete (m_nstring); + delete (m_number); + + for (std::vector <body_extension*>::iterator it = m_body_extensions.begin() ; + it != m_body_extensions.end() ; ++it) + { + delete (*it); + } + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + size_t pos = *currentPos; + + if (parser.check <one_char <'('> >(line, &pos, true)) + { + m_body_extensions.push_back + (parser.get <body_extension>(line, &pos)); + + while (!parser.check <one_char <')'> >(line, &pos, true)) + { + m_body_extensions.push_back(parser.get <body_extension>(line, &pos)); + parser.check <SPACE>(line, &pos, true); + } + } + else + { + if (!(m_nstring = parser.get <IMAPParser::nstring>(line, &pos, true))) + m_number = parser.get <IMAPParser::number>(line, &pos); + } + + *currentPos = pos; + } + + private: + + IMAPParser::nstring* m_nstring; + IMAPParser::number* m_number; + + std::vector <body_extension*> m_body_extensions; + + public: + + IMAPParser::nstring* nstring() const { return (m_nstring); } + IMAPParser::number* number() const { return (m_number); } + + const std::vector <body_extension*>& body_extensions() const { return (m_body_extensions); } + }; + + + // + // section_text ::= "HEADER" / "HEADER.FIELDS" [".NOT"] + // SPACE header_list / "TEXT" / "MIME" + // + + class section_text : public component + { + public: + + section_text() + : m_header_list(NULL) + { + } + + ~section_text() + { + delete (m_header_list); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("section_text"); + + size_t pos = *currentPos; + + // "HEADER.FIELDS" [".NOT"] SPACE header_list + const bool b1 = parser.checkWithArg <special_atom>(line, &pos, "header.fields.not", true); + const bool b2 = (b1 ? false : parser.checkWithArg <special_atom>(line, &pos, "header.fields", true)); + + if (b1 || b2) + { + m_type = b1 ? HEADER_FIELDS_NOT : HEADER_FIELDS; + + parser.check <SPACE>(line, &pos); + m_header_list = parser.get <IMAPParser::header_list>(line, &pos); + } + // "HEADER" + else if (parser.checkWithArg <special_atom>(line, &pos, "header", true)) + { + m_type = HEADER; + } + // "MIME" + else if (parser.checkWithArg <special_atom>(line, &pos, "mime", true)) + { + m_type = MIME; + } + // "TEXT" + else + { + m_type = TEXT; + + parser.checkWithArg <special_atom>(line, &pos, "text"); + } + + *currentPos = pos; + } + + + enum Type + { + HEADER, + HEADER_FIELDS, + HEADER_FIELDS_NOT, + MIME, + TEXT + }; + + private: + + Type m_type; + IMAPParser::header_list* m_header_list; + + public: + + Type type() const { return (m_type); } + const IMAPParser::header_list* header_list() const { return (m_header_list); } + }; + + + // + // section ::= "[" [section_text / (nz_number *["." nz_number] + // ["." (section_text / "MIME")])] "]" + // + + class section : public component + { + public: + + section() + : m_section_text1(NULL), m_section_text2(NULL) + { + } + + ~section() + { + delete (m_section_text1); + delete (m_section_text2); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("section"); + + size_t pos = *currentPos; + + parser.check <one_char <'['> >(line, &pos); + + if (!parser.check <one_char <']'> >(line, &pos, true)) + { + if (!(m_section_text1 = parser.get <section_text>(line, &pos, true))) + { + nz_number* num = parser.get <nz_number>(line, &pos); + m_nz_numbers.push_back(static_cast <unsigned int>(num->value())); + delete (num); + + while (parser.check <one_char <'.'> >(line, &pos, true)) + { + if ((num = parser.get <nz_number>(line, &pos, true))) + { + m_nz_numbers.push_back(static_cast <unsigned int>(num->value())); + delete (num); + } + else + { + m_section_text2 = parser.get <section_text>(line, &pos); + break; + } + } + } + + parser.check <one_char <']'> >(line, &pos); + } + + *currentPos = pos; + } + + private: + + section_text* m_section_text1; + section_text* m_section_text2; + std::vector <unsigned int> m_nz_numbers; + + public: + + const section_text* section_text1() const { return (m_section_text1); } + const section_text* section_text2() const { return (m_section_text2); } + const std::vector <unsigned int>& nz_numbers() const { return (m_nz_numbers); } + }; + + + // + // addr_adl ::= nstring + // ;; Holds route from [RFC-822] route-addr if + // ;; non-NIL + // + // addr_host ::= nstring + // ;; NIL indicates [RFC-822] group syntax. + // ;; Otherwise, holds [RFC-822] domain name + // + // addr_mailbox ::= nstring + // ;; NIL indicates end of [RFC-822] group; if + // ;; non-NIL and addr_host is NIL, holds + // ;; [RFC-822] group name. + // ;; Otherwise, holds [RFC-822] local-part + // + // addr_name ::= nstring + // ;; Holds phrase from [RFC-822] mailbox if + // ;; non-NIL + // + // address ::= "(" addr_name SPACE addr_adl SPACE addr_mailbox + // SPACE addr_host ")" + // + + class address : public component + { + public: + + address() + : m_addr_name(NULL), m_addr_adl(NULL), + m_addr_mailbox(NULL), m_addr_host(NULL) + { + } + + ~address() + { + delete (m_addr_name); + delete (m_addr_adl); + delete (m_addr_mailbox); + delete (m_addr_host); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("address"); + + size_t pos = *currentPos; + + parser.check <one_char <'('> >(line, &pos); + m_addr_name = parser.get <nstring>(line, &pos); + parser.check <SPACE>(line, &pos); + m_addr_adl = parser.get <nstring>(line, &pos); + parser.check <SPACE>(line, &pos); + m_addr_mailbox = parser.get <nstring>(line, &pos); + parser.check <SPACE>(line, &pos); + m_addr_host = parser.get <nstring>(line, &pos); + parser.check <one_char <')'> >(line, &pos); + + *currentPos = pos; + } + + private: + + nstring* m_addr_name; + nstring* m_addr_adl; + nstring* m_addr_mailbox; + nstring* m_addr_host; + + public: + + nstring* addr_name() const { return (m_addr_name); } + nstring* addr_adl() const { return (m_addr_adl); } + nstring* addr_mailbox() const { return (m_addr_mailbox); } + nstring* addr_host() const { return (m_addr_host); } + }; + + + // + // address_list ::= "(" 1*address ")" / nil + // + + class address_list : public component + { + public: + + ~address_list() + { + for (std::vector <address*>::iterator it = m_addresses.begin() ; + it != m_addresses.end() ; ++it) + { + delete (*it); + } + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("address_list"); + + size_t pos = *currentPos; + + if (!parser.check <NIL>(line, &pos, true)) + { + parser.check <one_char <'('> >(line, &pos); + + while (!parser.check <one_char <')'> >(line, &pos, true)) + { + m_addresses.push_back(parser.get <address>(line, &pos)); + parser.check <SPACE>(line, &pos, true); + } + } + + *currentPos = pos; + } + + private: + + std::vector <address*> m_addresses; + + public: + + const std::vector <address*>& addresses() const { return (m_addresses); } + }; + + + // + // env_bcc ::= "(" 1*address ")" / nil + // + + COMPONENT_ALIAS(address_list, env_bcc); + + + // + // env_cc ::= "(" 1*address ")" / nil + // + + COMPONENT_ALIAS(address_list, env_cc); + + + // + // env_date ::= nstring + // + + COMPONENT_ALIAS(nstring, env_date); + + + // + // env_from ::= "(" 1*address ")" / nil + // + + COMPONENT_ALIAS(address_list, env_from); + + + // + // env_in_reply_to ::= nstring + // + + COMPONENT_ALIAS(nstring, env_in_reply_to); + + + // + // env_message_id ::= nstring + // + + COMPONENT_ALIAS(nstring, env_message_id); + + + // + // env_reply_to ::= "(" 1*address ")" / nil + // + + COMPONENT_ALIAS(address_list, env_reply_to); + + + // + // env_sender ::= "(" 1*address ")" / nil + // + + COMPONENT_ALIAS(address_list, env_sender); + + + // + // env_subject ::= nstring + // + + COMPONENT_ALIAS(nstring, env_subject); + + + // + // env_to ::= "(" 1*address ")" / nil + // + + COMPONENT_ALIAS(address_list, env_to); + + + // + // envelope ::= "(" env_date SPACE env_subject SPACE env_from + // SPACE env_sender SPACE env_reply_to SPACE env_to + // SPACE env_cc SPACE env_bcc SPACE env_in_reply_to + // SPACE env_message_id ")" + // + + class envelope : public component + { + public: + + envelope() + : m_env_date(NULL), m_env_subject(NULL), + m_env_from(NULL), m_env_sender(NULL), m_env_reply_to(NULL), + m_env_to(NULL), m_env_cc(NULL), m_env_bcc(NULL), + m_env_in_reply_to(NULL), m_env_message_id(NULL) + { + } + + ~envelope() + { + delete (m_env_date); + delete (m_env_subject); + delete (m_env_from); + delete (m_env_sender); + delete (m_env_reply_to); + delete (m_env_to); + delete (m_env_cc); + delete (m_env_bcc); + delete (m_env_in_reply_to); + delete (m_env_message_id); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("envelope"); + + size_t pos = *currentPos; + + parser.check <one_char <'('> >(line, &pos); + + m_env_date = parser.get <IMAPParser::env_date>(line, &pos); + parser.check <SPACE>(line, &pos); + + m_env_subject = parser.get <IMAPParser::env_subject>(line, &pos); + parser.check <SPACE>(line, &pos); + + m_env_from = parser.get <IMAPParser::env_from>(line, &pos); + parser.check <SPACE>(line, &pos); + + m_env_sender = parser.get <IMAPParser::env_sender>(line, &pos); + parser.check <SPACE>(line, &pos); + + m_env_reply_to = parser.get <IMAPParser::env_reply_to>(line, &pos); + parser.check <SPACE>(line, &pos); + + m_env_to = parser.get <IMAPParser::env_to>(line, &pos); + parser.check <SPACE>(line, &pos); + + m_env_cc = parser.get <IMAPParser::env_cc>(line, &pos); + parser.check <SPACE>(line, &pos); + + m_env_bcc = parser.get <IMAPParser::env_bcc>(line, &pos); + parser.check <SPACE>(line, &pos); + + m_env_in_reply_to = parser.get <IMAPParser::env_in_reply_to>(line, &pos); + parser.check <SPACE>(line, &pos); + + m_env_message_id = parser.get <IMAPParser::env_message_id>(line, &pos); + + parser.check <one_char <')'> >(line, &pos); + + *currentPos = pos; + } + + private: + + IMAPParser::env_date* m_env_date; + IMAPParser::env_subject* m_env_subject; + IMAPParser::env_from* m_env_from; + IMAPParser::env_sender* m_env_sender; + IMAPParser::env_reply_to* m_env_reply_to; + IMAPParser::env_to* m_env_to; + IMAPParser::env_cc* m_env_cc; + IMAPParser::env_bcc* m_env_bcc; + IMAPParser::env_in_reply_to* m_env_in_reply_to; + IMAPParser::env_message_id* m_env_message_id; + + public: + + const IMAPParser::env_date* env_date() const { return (m_env_date); } + const IMAPParser::env_subject* env_subject() const { return (m_env_subject); } + const IMAPParser::env_from* env_from() const { return (m_env_from); } + const IMAPParser::env_sender* env_sender() const { return (m_env_sender); } + const IMAPParser::env_reply_to* env_reply_to() const { return (m_env_reply_to); } + const IMAPParser::env_to* env_to() const { return (m_env_to); } + const IMAPParser::env_cc* env_cc() const { return (m_env_cc); } + const IMAPParser::env_bcc* env_bcc() const { return (m_env_bcc); } + const IMAPParser::env_in_reply_to* env_in_reply_to() const { return (m_env_in_reply_to); } + const IMAPParser::env_message_id* env_message_id() const { return (m_env_message_id); } + }; + + + // + // body_fld_desc ::= nstring + // + + typedef nstring body_fld_desc; + + + // + // body_fld_id ::= nstring + // + + typedef nstring body_fld_id; + + + // + // body_fld_md5 ::= nstring + // + + typedef nstring body_fld_md5; + + + // + // body_fld_octets ::= number + // + + typedef number body_fld_octets; + + + // + // body_fld_lines ::= number + // + + typedef number body_fld_lines; + + + // + // body_fld_enc ::= (<"> ("7BIT" / "8BIT" / "BINARY" / "BASE64"/ + // "QUOTED-PRINTABLE") <">) / string + // + + typedef xstring body_fld_enc; + + + // + // body_fld_param_item ::= string SPACE string + // + + class body_fld_param_item : public component + { + public: + + body_fld_param_item() + : m_string1(NULL), m_string2(NULL) + { + } + + ~body_fld_param_item() + { + delete (m_string1); + delete (m_string2); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("body_fld_param_item"); + + size_t pos = *currentPos; + + if (!parser.isStrict()) + { + // Some servers send an <atom> instead of a <string> here: + // eg. ... (CHARSET "X-UNKNOWN") ... + if (!(m_string1 = parser.get <xstring>(line, &pos, true))) + { + std::auto_ptr <atom> at(parser.get <atom>(line, &pos)); + + m_string1 = new xstring(); + m_string1->setValue(at->value()); + } + } + else + { + m_string1 = parser.get <xstring>(line, &pos); + } + + parser.check <SPACE>(line, &pos); + m_string2 = parser.get <xstring>(line, &pos); + + DEBUG_FOUND("body_fld_param_item", "<" << m_string1->value() << ", " << m_string2->value() << ">"); + + *currentPos = pos; + } + + private: + + xstring* m_string1; + xstring* m_string2; + + public: + + const xstring* string1() const { return (m_string1); } + const xstring* string2() const { return (m_string2); } + }; + + + // + // body_fld_param ::= "(" 1#(body_fld_param_item) ")" / nil + // + + class body_fld_param : public component + { + public: + + ~body_fld_param() + { + for (std::vector <body_fld_param_item*>::iterator it = m_items.begin() ; + it != m_items.end() ; ++it) + { + delete (*it); + } + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("body_fld_param"); + + size_t pos = *currentPos; + + if (parser.check <one_char <'('> >(line, &pos, true)) + { + m_items.push_back(parser.get <body_fld_param_item>(line, &pos)); + + while (!parser.check <one_char <')'> >(line, &pos, true)) + { + parser.check <SPACE>(line, &pos); + m_items.push_back(parser.get <body_fld_param_item>(line, &pos)); + } + } + else + { + parser.check <NIL>(line, &pos); + } + + *currentPos = pos; + } + + private: + + std::vector <body_fld_param_item*> m_items; + + public: + + const std::vector <body_fld_param_item*>& items() const { return (m_items); } + }; + + + // + // body_fld_dsp ::= "(" string SPACE body_fld_param ")" / nil + // + + class body_fld_dsp : public component + { + public: + + body_fld_dsp() + : m_string(NULL), m_body_fld_param(NULL) + { + } + + ~body_fld_dsp() + { + delete (m_string); + delete (m_body_fld_param); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("body_fld_dsp"); + + size_t pos = *currentPos; + + if (parser.check <one_char <'('> >(line, &pos, true)) + { + m_string = parser.get <xstring>(line, &pos); + parser.check <SPACE>(line, &pos); + m_body_fld_param = parser.get <class body_fld_param>(line, &pos); + parser.check <one_char <')'> >(line, &pos); + } + else + { + parser.check <NIL>(line, &pos); + } + + *currentPos = pos; + } + + private: + + class xstring* m_string; + class body_fld_param* m_body_fld_param; + + public: + + const class xstring* str() const { return (m_string); } + const class body_fld_param* body_fld_param() const { return (m_body_fld_param); } + }; + + + // + // body_fld_lang ::= nstring / "(" 1#string ")" + // + + class body_fld_lang : public component + { + public: + + ~body_fld_lang() + { + for (std::vector <xstring*>::iterator it = m_strings.begin() ; + it != m_strings.end() ; ++it) + { + delete (*it); + } + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("body_fld_lang"); + + size_t pos = *currentPos; + + if (parser.check <one_char <'('> >(line, &pos, true)) + { + m_strings.push_back(parser.get <class xstring>(line, &pos)); + + while (!parser.check <one_char <')'> >(line, &pos, true)) + { + parser.check <SPACE>(line, &pos); + m_strings.push_back(parser.get <class xstring>(line, &pos)); + } + } + else + { + m_strings.push_back(parser.get <class nstring>(line, &pos)); + } + + *currentPos = pos; + } + + private: + + std::vector <xstring*> m_strings; + + public: + + const std::vector <xstring*>& strings() const { return (m_strings); } + }; + + + // + // body_fields ::= body_fld_param SPACE body_fld_id SPACE + // body_fld_desc SPACE body_fld_enc SPACE + // body_fld_octets + // + + class body_fields : public component + { + public: + + body_fields() + : m_body_fld_param(NULL), m_body_fld_id(NULL), + m_body_fld_desc(NULL), m_body_fld_enc(NULL), m_body_fld_octets(NULL) + { + } + + ~body_fields() + { + delete (m_body_fld_param); + delete (m_body_fld_id); + delete (m_body_fld_desc); + delete (m_body_fld_enc); + delete (m_body_fld_octets); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("body_fields"); + + size_t pos = *currentPos; + + m_body_fld_param = parser.get <IMAPParser::body_fld_param>(line, &pos); + parser.check <SPACE>(line, &pos); + m_body_fld_id = parser.get <IMAPParser::body_fld_id>(line, &pos); + parser.check <SPACE>(line, &pos); + m_body_fld_desc = parser.get <IMAPParser::body_fld_desc>(line, &pos); + parser.check <SPACE>(line, &pos); + m_body_fld_enc = parser.get <IMAPParser::body_fld_enc>(line, &pos); + parser.check <SPACE>(line, &pos); + m_body_fld_octets = parser.get <IMAPParser::body_fld_octets>(line, &pos); + + *currentPos = pos; + } + + private: + + IMAPParser::body_fld_param* m_body_fld_param; + IMAPParser::body_fld_id* m_body_fld_id; + IMAPParser::body_fld_desc* m_body_fld_desc; + IMAPParser::body_fld_enc* m_body_fld_enc; + IMAPParser::body_fld_octets* m_body_fld_octets; + + public: + + const IMAPParser::body_fld_param* body_fld_param() const { return (m_body_fld_param); } + const IMAPParser::body_fld_id* body_fld_id() const { return (m_body_fld_id); } + const IMAPParser::body_fld_desc* body_fld_desc() const { return (m_body_fld_desc); } + const IMAPParser::body_fld_enc* body_fld_enc() const { return (m_body_fld_enc); } + const IMAPParser::body_fld_octets* body_fld_octets() const { return (m_body_fld_octets); } + }; + + + // + // media_subtype ::= string + // ;; Defined in [MIME-IMT] + // + + typedef xstring media_subtype; + + + // + // media_text ::= <"> "TEXT" <"> SPACE media_subtype + // ;; Defined in [MIME-IMT] + // + + class media_text : public component + { + public: + + media_text() + : m_media_subtype(NULL) + { + } + + ~media_text() + { + delete (m_media_subtype); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("media_text"); + + size_t pos = *currentPos; + + parser.check <one_char <'"'> >(line, &pos); + parser.checkWithArg <special_atom>(line, &pos, "text"); + parser.check <one_char <'"'> >(line, &pos); + parser.check <SPACE>(line, &pos); + + m_media_subtype = parser.get <IMAPParser::media_subtype>(line, &pos); + + *currentPos = pos; + } + + private: + + IMAPParser::media_subtype* m_media_subtype; + + public: + + const IMAPParser::media_subtype* media_subtype() const { return (m_media_subtype); } + }; + + + // + // media_message ::= <"> "MESSAGE" <"> SPACE <"> "RFC822" <"> + // ;; Defined in [MIME-IMT] + // + + class media_message : public component + { + public: + + media_message() + : m_media_subtype(NULL) + { + } + + ~media_message() + { + delete m_media_subtype; + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("media_message"); + + size_t pos = *currentPos; + + parser.check <one_char <'"'> >(line, &pos); + parser.checkWithArg <special_atom>(line, &pos, "message"); + parser.check <one_char <'"'> >(line, &pos); + parser.check <SPACE>(line, &pos); + + //parser.check <one_char <'"'> >(line, &pos); + //parser.checkWithArg <special_atom>(line, &pos, "rfc822"); + //parser.check <one_char <'"'> >(line, &pos); + + m_media_subtype = parser.get <IMAPParser::media_subtype>(line, &pos); + + *currentPos = pos; + } + + private: + + IMAPParser::media_subtype* m_media_subtype; + + public: + + const IMAPParser::media_subtype* media_subtype() const { return (m_media_subtype); } + }; + + + // + // media_basic ::= (<"> ("APPLICATION" / "AUDIO" / "IMAGE" / + // "MESSAGE" / "VIDEO") <">) / string) + // SPACE media_subtype + // ;; Defined in [MIME-IMT] + + class media_basic : public component + { + public: + + media_basic() + : m_media_type(NULL), m_media_subtype(NULL) + { + } + + ~media_basic() + { + delete (m_media_type); + delete (m_media_subtype); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("media_basic"); + + size_t pos = *currentPos; + + m_media_type = parser.get <xstring>(line, &pos); + + parser.check <SPACE>(line, &pos); + + m_media_subtype = parser.get <IMAPParser::media_subtype>(line, &pos); + + *currentPos = pos; + } + + private: + + IMAPParser::xstring* m_media_type; + IMAPParser::media_subtype* m_media_subtype; + + public: + + const IMAPParser::xstring* media_type() const { return (m_media_type); } + const IMAPParser::media_subtype* media_subtype() const { return (m_media_subtype); } + }; + + + // + // body_ext_1part ::= body_fld_md5 [SPACE body_fld_dsp + // [SPACE body_fld_lang + // [SPACE 1#body_extension]]] + // ;; MUST NOT be returned on non-extensible + // ;; "BODY" fetch + // + + class body_ext_1part : public component + { + public: + + body_ext_1part() + : m_body_fld_md5(NULL), m_body_fld_dsp(NULL), m_body_fld_lang(NULL) + { + } + + ~body_ext_1part() + { + delete (m_body_fld_md5); + delete (m_body_fld_dsp); + delete (m_body_fld_lang); + + for (std::vector <body_extension*>::iterator it = m_body_extensions.begin() ; + it != m_body_extensions.end() ; ++it) + { + delete (*it); + } + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("body_ext_1part"); + + size_t pos = *currentPos; + + m_body_fld_md5 = parser.get <IMAPParser::body_fld_md5>(line, &pos); + + // [SPACE body_fld_dsp + if (parser.check <SPACE>(line, &pos, true)) + { + m_body_fld_dsp = parser.get <IMAPParser::body_fld_dsp>(line, &pos); + + // [SPACE body_fld_lang + if (parser.check <SPACE>(line, &pos, true)) + { + m_body_fld_lang = parser.get <IMAPParser::body_fld_lang>(line, &pos); + + // [SPACE 1#body_extension] + if (parser.check <SPACE>(line, &pos, true)) + { + m_body_extensions.push_back + (parser.get <body_extension>(line, &pos)); + + parser.check <SPACE>(line, &pos, true); + + body_extension* ext = NULL; + + while ((ext = parser.get <body_extension>(line, &pos, true)) != NULL) + { + m_body_extensions.push_back(ext); + parser.check <SPACE>(line, &pos, true); + } + } + } + } + + *currentPos = pos; + } + + private: + + IMAPParser::body_fld_md5* m_body_fld_md5; + IMAPParser::body_fld_dsp* m_body_fld_dsp; + IMAPParser::body_fld_lang* m_body_fld_lang; + + std::vector <body_extension*> m_body_extensions; + + public: + + const IMAPParser::body_fld_md5* body_fld_md5() const { return (m_body_fld_md5); } + const IMAPParser::body_fld_dsp* body_fld_dsp() const { return (m_body_fld_dsp); } + const IMAPParser::body_fld_lang* body_fld_lang() const { return (m_body_fld_lang); } + + const std::vector <body_extension*> body_extensions() const { return (m_body_extensions); } + }; + + + // + // body_ext_mpart ::= body_fld_param + // [SPACE body_fld_dsp SPACE body_fld_lang + // [SPACE 1#body_extension]] + // ;; MUST NOT be returned on non-extensible + // ;; "BODY" fetch + + class body_ext_mpart : public component + { + public: + + body_ext_mpart() + : m_body_fld_param(NULL), m_body_fld_dsp(NULL), m_body_fld_lang(NULL) + { + } + + ~body_ext_mpart() + { + delete (m_body_fld_param); + delete (m_body_fld_dsp); + delete (m_body_fld_lang); + + for (std::vector <body_extension*>::iterator it = m_body_extensions.begin() ; + it != m_body_extensions.end() ; ++it) + { + delete (*it); + } + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("body_ext_mpart"); + + size_t pos = *currentPos; + + m_body_fld_param = parser.get <IMAPParser::body_fld_param>(line, &pos); + + // [SPACE body_fld_dsp SPACE body_fld_lang [SPACE 1#body_extension]] + if (parser.check <SPACE>(line, &pos, true)) + { + m_body_fld_dsp = parser.get <IMAPParser::body_fld_dsp>(line, &pos); + parser.check <SPACE>(line, &pos); + m_body_fld_lang = parser.get <IMAPParser::body_fld_lang>(line, &pos); + + // [SPACE 1#body_extension] + if (parser.check <SPACE>(line, &pos, true)) + { + m_body_extensions.push_back + (parser.get <body_extension>(line, &pos)); + + parser.check <SPACE>(line, &pos, true); + + body_extension* ext = NULL; + + while ((ext = parser.get <body_extension>(line, &pos, true)) != NULL) + { + m_body_extensions.push_back(ext); + parser.check <SPACE>(line, &pos, true); + } + } + } + + *currentPos = pos; + } + + private: + + IMAPParser::body_fld_param* m_body_fld_param; + IMAPParser::body_fld_dsp* m_body_fld_dsp; + IMAPParser::body_fld_lang* m_body_fld_lang; + + std::vector <body_extension*> m_body_extensions; + + public: + + const IMAPParser::body_fld_param* body_fld_param() const { return (m_body_fld_param); } + const IMAPParser::body_fld_dsp* body_fld_dsp() const { return (m_body_fld_dsp); } + const IMAPParser::body_fld_lang* body_fld_lang() const { return (m_body_fld_lang); } + + const std::vector <body_extension*> body_extensions() const { return (m_body_extensions); } + }; + + + // + // body_type_basic ::= media_basic SPACE body_fields + // ;; MESSAGE subtype MUST NOT be "RFC822" + // + + class body_type_basic : public component + { + public: + + body_type_basic() + : m_media_basic(NULL), m_body_fields(NULL) + { + } + + ~body_type_basic() + { + delete (m_media_basic); + delete (m_body_fields); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("body_type_basic"); + + size_t pos = *currentPos; + + m_media_basic = parser.get <IMAPParser::media_basic>(line, &pos); + parser.check <SPACE>(line, &pos); + m_body_fields = parser.get <IMAPParser::body_fields>(line, &pos); + + *currentPos = pos; + } + + private: + + IMAPParser::media_basic* m_media_basic; + IMAPParser::body_fields* m_body_fields; + + public: + + const IMAPParser::media_basic* media_basic() const { return (m_media_basic); } + const IMAPParser::body_fields* body_fields() const { return (m_body_fields); } + }; + + + // + // body_type_msg ::= media_message SPACE body_fields SPACE envelope + // SPACE body SPACE body_fld_lines + // + + class xbody; + typedef xbody body; + + class body_type_msg : public component + { + public: + + body_type_msg() + : m_media_message(NULL), m_body_fields(NULL), + m_envelope(NULL), m_body(NULL), m_body_fld_lines(NULL) + { + } + + ~body_type_msg() + { + delete (m_media_message); + delete (m_body_fields); + delete (m_envelope); + delete (m_body); + delete (m_body_fld_lines); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("body_type_msg"); + + size_t pos = *currentPos; + + m_media_message = parser.get <IMAPParser::media_message>(line, &pos); + parser.check <SPACE>(line, &pos); + m_body_fields = parser.get <IMAPParser::body_fields>(line, &pos); + parser.check <SPACE>(line, &pos); + + // BUGFIX: made SPACE optional. This is not standard, but some servers + // seem to return responses like that... + m_envelope = parser.get <IMAPParser::envelope>(line, &pos); + parser.check <SPACE>(line, &pos, true); + m_body = parser.get <IMAPParser::xbody>(line, &pos); + parser.check <SPACE>(line, &pos, true); + m_body_fld_lines = parser.get <IMAPParser::body_fld_lines>(line, &pos); + + *currentPos = pos; + } + + private: + + IMAPParser::media_message* m_media_message; + IMAPParser::body_fields* m_body_fields; + IMAPParser::envelope* m_envelope; + IMAPParser::xbody* m_body; + IMAPParser::body_fld_lines* m_body_fld_lines; + + public: + + const IMAPParser::media_message* media_message() const { return (m_media_message); } + const IMAPParser::body_fields* body_fields() const { return (m_body_fields); } + const IMAPParser::envelope* envelope() const { return (m_envelope); } + const IMAPParser::xbody* body() const { return (m_body); } + const IMAPParser::body_fld_lines* body_fld_lines() const { return (m_body_fld_lines); } + }; + + + // + // body_type_text ::= media_text SPACE body_fields SPACE body_fld_lines + // + + class body_type_text : public component + { + public: + + body_type_text() + : m_media_text(NULL), + m_body_fields(NULL), m_body_fld_lines(NULL) + { + } + + ~body_type_text() + { + delete (m_media_text); + delete (m_body_fields); + delete (m_body_fld_lines); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("body_type_text"); + + size_t pos = *currentPos; + + m_media_text = parser.get <IMAPParser::media_text>(line, &pos); + parser.check <SPACE>(line, &pos); + m_body_fields = parser.get <IMAPParser::body_fields>(line, &pos); + parser.check <SPACE>(line, &pos); + m_body_fld_lines = parser.get <IMAPParser::body_fld_lines>(line, &pos); + + *currentPos = pos; + } + + private: + + IMAPParser::media_text* m_media_text; + IMAPParser::body_fields* m_body_fields; + IMAPParser::body_fld_lines* m_body_fld_lines; + + public: + + const IMAPParser::media_text* media_text() const { return (m_media_text); } + const IMAPParser::body_fields* body_fields() const { return (m_body_fields); } + const IMAPParser::body_fld_lines* body_fld_lines() const { return (m_body_fld_lines); } + }; + + + // + // body_type_1part ::= (body_type_basic / body_type_msg / body_type_text) + // [SPACE body_ext_1part] + // + + class body_type_1part : public component + { + public: + + body_type_1part() + : m_body_type_basic(NULL), m_body_type_msg(NULL), + m_body_type_text(NULL), m_body_ext_1part(NULL) + { + } + + ~body_type_1part() + { + delete (m_body_type_basic); + delete (m_body_type_msg); + delete (m_body_type_text); + + delete (m_body_ext_1part); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("body_type_1part"); + + size_t pos = *currentPos; + + if (!(m_body_type_text = parser.get <IMAPParser::body_type_text>(line, &pos, true))) + if (!(m_body_type_msg = parser.get <IMAPParser::body_type_msg>(line, &pos, true))) + m_body_type_basic = parser.get <IMAPParser::body_type_basic>(line, &pos); + + if (parser.check <SPACE>(line, &pos, true)) + { + m_body_ext_1part = parser.get <IMAPParser::body_ext_1part>(line, &pos, true); + + if (!m_body_ext_1part) + --pos; + } + + *currentPos = pos; + } + + private: + + IMAPParser::body_type_basic* m_body_type_basic; + IMAPParser::body_type_msg* m_body_type_msg; + IMAPParser::body_type_text* m_body_type_text; + + IMAPParser::body_ext_1part* m_body_ext_1part; + + public: + + const IMAPParser::body_type_basic* body_type_basic() const { return (m_body_type_basic); } + const IMAPParser::body_type_msg* body_type_msg() const { return (m_body_type_msg); } + const IMAPParser::body_type_text* body_type_text() const { return (m_body_type_text); } + + const IMAPParser::body_ext_1part* body_ext_1part() const { return (m_body_ext_1part); } + }; + + + // + // body_type_mpart ::= 1*body SPACE media_subtype + // [SPACE body_ext_mpart] + // + + class body_type_mpart : public component + { + public: + + body_type_mpart() + : m_media_subtype(NULL), m_body_ext_mpart(NULL) + { + } + + ~body_type_mpart() + { + delete (m_media_subtype); + delete (m_body_ext_mpart); + + for (std::vector <xbody*>::iterator it = m_list.begin() ; + it != m_list.end() ; ++it) + { + delete (*it); + } + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("body_type_mpart"); + + size_t pos = *currentPos; + + m_list.push_back(parser.get <xbody>(line, &pos)); + + for (xbody* b ; (b = parser.get <xbody>(line, &pos, true)) ; ) + m_list.push_back(b); + + parser.check <SPACE>(line, &pos); + + m_media_subtype = parser.get <IMAPParser::media_subtype>(line, &pos); + + if (parser.check <SPACE>(line, &pos, true)) + m_body_ext_mpart = parser.get <IMAPParser::body_ext_mpart>(line, &pos); + + *currentPos = pos; + } + + private: + + IMAPParser::media_subtype* m_media_subtype; + IMAPParser::body_ext_mpart* m_body_ext_mpart; + + std::vector <xbody*> m_list; + + public: + + const std::vector <IMAPParser::xbody*>& list() const { return (m_list); } + + const IMAPParser::media_subtype* media_subtype() const { return (m_media_subtype); } + const IMAPParser::body_ext_mpart* body_ext_mpart() const { return (m_body_ext_mpart); } + }; + + + // + // xbody ::= "(" body_type_1part / body_type_mpart ")" + // + + class xbody : public component + { + public: + + xbody() + : m_body_type_1part(NULL), m_body_type_mpart(NULL) + { + } + + ~xbody() + { + delete (m_body_type_1part); + delete (m_body_type_mpart); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("body"); + + size_t pos = *currentPos; + + parser.check <one_char <'('> >(line, &pos); + + if (!(m_body_type_mpart = parser.get <IMAPParser::body_type_mpart>(line, &pos, true))) + m_body_type_1part = parser.get <IMAPParser::body_type_1part>(line, &pos); + + parser.check <one_char <')'> >(line, &pos); + + *currentPos = pos; + } + + private: + + IMAPParser::body_type_1part* m_body_type_1part; + IMAPParser::body_type_mpart* m_body_type_mpart; + + public: + + const IMAPParser::body_type_1part* body_type_1part() const { return (m_body_type_1part); } + const IMAPParser::body_type_mpart* body_type_mpart() const { return (m_body_type_mpart); } + }; + + + // + // uniqueid ::= nz_number + // ;; Strictly ascending + // + // msg_att_item ::= "ENVELOPE" SPACE envelope / + // "FLAGS" SPACE "(" #(flag / "\Recent") ")" / + // "INTERNALDATE" SPACE date_time / + // "RFC822" [".HEADER" / ".TEXT"] SPACE nstring / + // "RFC822.SIZE" SPACE number / + // "BODY" ["STRUCTURE"] SPACE body / + // "BODY" section ["<" number ">"] SPACE nstring / + // "UID" SPACE uniqueid + // + // IMAP Extension for Conditional STORE (RFC-4551): + // + // msg_att_item /= "MODSEQ" SP "(" mod_sequence_value ")" + + class msg_att_item : public component + { + public: + + msg_att_item() + : m_date_time(NULL), m_number(NULL), m_envelope(NULL), + m_uniqueid(NULL), m_nstring(NULL), m_body(NULL), m_flag_list(NULL), + m_section(NULL), m_mod_sequence_value(NULL) + + { + } + + ~msg_att_item() + { + delete (m_date_time); + delete (m_number); + delete (m_envelope); + delete (m_uniqueid); + delete (m_nstring); + delete (m_body); + delete (m_flag_list); + delete (m_section); + delete m_mod_sequence_value; + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("msg_att_item"); + + size_t pos = *currentPos; + + // "ENVELOPE" SPACE envelope + if (parser.checkWithArg <special_atom>(line, &pos, "envelope", true)) + { + m_type = ENVELOPE; + + parser.check <SPACE>(line, &pos); + m_envelope = parser.get <IMAPParser::envelope>(line, &pos); + } + // "FLAGS" SPACE "(" #(flag / "\Recent") ")" + else if (parser.checkWithArg <special_atom>(line, &pos, "flags", true)) + { + m_type = FLAGS; + + parser.check <SPACE>(line, &pos); + + m_flag_list = parser.get <IMAPParser::flag_list>(line, &pos); + } + // "INTERNALDATE" SPACE date_time + else if (parser.checkWithArg <special_atom>(line, &pos, "internaldate", true)) + { + m_type = INTERNALDATE; + + parser.check <SPACE>(line, &pos); + m_date_time = parser.get <IMAPParser::date_time>(line, &pos); + } + // "RFC822" ".HEADER" SPACE nstring + else if (parser.checkWithArg <special_atom>(line, &pos, "rfc822.header", true)) + { + m_type = RFC822_HEADER; + + parser.check <SPACE>(line, &pos); + + m_nstring = parser.get <IMAPParser::nstring>(line, &pos); + } + // "RFC822" ".TEXT" SPACE nstring + else if (parser.checkWithArg <special_atom>(line, &pos, "rfc822.text", true)) + { + m_type = RFC822_TEXT; + + parser.check <SPACE>(line, &pos); + + m_nstring = parser.getWithArgs <IMAPParser::nstring> + (line, &pos, this, RFC822_TEXT); + } + // "RFC822.SIZE" SPACE number + else if (parser.checkWithArg <special_atom>(line, &pos, "rfc822.size", true)) + { + m_type = RFC822_SIZE; + + parser.check <SPACE>(line, &pos); + m_number = parser.get <IMAPParser::number>(line, &pos); + } + // "RFC822" SPACE nstring + else if (parser.checkWithArg <special_atom>(line, &pos, "rfc822", true)) + { + m_type = RFC822; + + parser.check <SPACE>(line, &pos); + + m_nstring = parser.get <IMAPParser::nstring>(line, &pos); + } + // "BODY" "STRUCTURE" SPACE body + else if (parser.checkWithArg <special_atom>(line, &pos, "bodystructure", true)) + { + m_type = BODY_STRUCTURE; + + parser.check <SPACE>(line, &pos); + + m_body = parser.get <IMAPParser::body>(line, &pos); + } + // "BODY" section ["<" number ">"] SPACE nstring + // "BODY" SPACE body + else if (parser.checkWithArg <special_atom>(line, &pos, "body", true)) + { + m_section = parser.get <IMAPParser::section>(line, &pos, true); + + // "BODY" section ["<" number ">"] SPACE nstring + if (m_section != NULL) + { + m_type = BODY_SECTION; + + if (parser.check <one_char <'<'> >(line, &pos, true)) + { + m_number = parser.get <IMAPParser::number>(line, &pos); + parser.check <one_char <'>'> >(line, &pos); + } + + parser.check <SPACE>(line, &pos); + + m_nstring = parser.getWithArgs <IMAPParser::nstring> + (line, &pos, this, BODY_SECTION); + } + // "BODY" SPACE body + else + { + m_type = BODY; + + parser.check <SPACE>(line, &pos); + + m_body = parser.get <IMAPParser::body>(line, &pos); + } + } + // "MODSEQ" SP "(" mod_sequence_value ")" + else if (parser.checkWithArg <special_atom>(line, &pos, "modseq", true)) + { + m_type = MODSEQ; + + parser.check <SPACE>(line, &pos); + parser.check <one_char <'('> >(line, &pos); + + m_mod_sequence_value = parser.get <IMAPParser::mod_sequence_value>(line, &pos); + + parser.check <one_char <')'> >(line, &pos); + } + // "UID" SPACE uniqueid + else + { + m_type = UID; + + parser.checkWithArg <special_atom>(line, &pos, "uid"); + parser.check <SPACE>(line, &pos); + + m_uniqueid = parser.get <nz_number>(line, &pos); + } + + *currentPos = pos; + } + + + enum Type + { + ENVELOPE, + FLAGS, + INTERNALDATE, + RFC822, + RFC822_SIZE, + RFC822_HEADER, + RFC822_TEXT, + BODY, + BODY_SECTION, + BODY_STRUCTURE, + UID, + MODSEQ + }; + + private: + + Type m_type; + + IMAPParser::date_time* m_date_time; + IMAPParser::number* m_number; + IMAPParser::envelope* m_envelope; + IMAPParser::nz_number* m_uniqueid; + IMAPParser::nstring* m_nstring; + IMAPParser::xbody* m_body; + IMAPParser::flag_list* m_flag_list; + IMAPParser::section* m_section; + IMAPParser::mod_sequence_value* m_mod_sequence_value; + + public: + + Type type() const { return (m_type); } + + const IMAPParser::date_time* date_time() const { return (m_date_time); } + const IMAPParser::number* number() const { return (m_number); } + const IMAPParser::envelope* envelope() const { return (m_envelope); } + const IMAPParser::nz_number* unique_id() const { return (m_uniqueid); } + const IMAPParser::nstring* nstring() const { return (m_nstring); } + const IMAPParser::xbody* body() const { return (m_body); } + const IMAPParser::flag_list* flag_list() const { return (m_flag_list); } + const IMAPParser::section* section() const { return (m_section); } + const IMAPParser::mod_sequence_value* mod_sequence_value() { return m_mod_sequence_value; } + }; + + + // + // msg_att ::= "(" 1#(msg_att_item) ")" + // + + class msg_att : public component + { + public: + + ~msg_att() + { + for (std::vector <msg_att_item*>::iterator it = m_items.begin() ; + it != m_items.end() ; ++it) + { + delete (*it); + } + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("msg_att"); + + size_t pos = *currentPos; + + parser.check <one_char <'('> >(line, &pos); + + m_items.push_back(parser.get <msg_att_item>(line, &pos)); + + while (!parser.check <one_char <')'> >(line, &pos, true)) + { + parser.check <SPACE>(line, &pos); + m_items.push_back(parser.get <msg_att_item>(line, &pos)); + } + + *currentPos = pos; + } + + private: + + std::vector <msg_att_item*> m_items; + + public: + + const std::vector <msg_att_item*>& items() const { return (m_items); } + }; + + + // + // message_data ::= nz_number SPACE ("EXPUNGE" / + // ("FETCH" SPACE msg_att)) + // + + class message_data : public component + { + public: + + message_data() + : m_number(0), m_msg_att(NULL) + { + } + + ~message_data() + { + delete (m_msg_att); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("message_data"); + + size_t pos = *currentPos; + + nz_number* num = parser.get <nz_number>(line, &pos); + m_number = static_cast <unsigned int>(num->value()); + delete (num); + + parser.check <SPACE>(line, &pos); + + if (parser.checkWithArg <special_atom>(line, &pos, "expunge", true)) + { + m_type = EXPUNGE; + } + else + { + parser.checkWithArg <special_atom>(line, &pos, "fetch"); + + parser.check <SPACE>(line, &pos); + + m_type = FETCH; + m_msg_att = parser.get <IMAPParser::msg_att>(line, &pos); + } + + *currentPos = pos; + } + + + enum Type + { + EXPUNGE, + FETCH + }; + + private: + + Type m_type; + unsigned int m_number; + IMAPParser::msg_att* m_msg_att; + + public: + + Type type() const { return (m_type); } + unsigned int number() const { return (m_number); } + const IMAPParser::msg_att* msg_att() const { return (m_msg_att); } + }; + + + // + // resp_text_code ::= "ALERT" / "PARSE" / + // capability-data / + // "PERMANENTFLAGS" SPACE "(" #(flag / "\*") ")" / + // "READ-ONLY" / "READ-WRITE" / "TRYCREATE" / + // "UIDVALIDITY" SPACE nz_number / + // "UNSEEN" SPACE nz_number / + // atom [SPACE 1*<any TEXT_CHAR except "]">] + // + // IMAP Extension for Conditional STORE (RFC-4551): + // + // resp-text-code =/ "HIGHESTMODSEQ" SP mod-sequence-value / + // "NOMODSEQ" / + // "MODIFIED" SP set + + class resp_text_code : public component + { + public: + + resp_text_code() + : m_nz_number(NULL), m_atom(NULL), m_flag_list(NULL), + m_text(NULL), m_capability_data(NULL) + { + } + + ~resp_text_code() + { + delete (m_nz_number); + delete (m_atom); + delete (m_flag_list); + delete (m_text); + delete m_capability_data; + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("resp_text_code"); + + size_t pos = *currentPos; + + // "ALERT" + if (parser.checkWithArg <special_atom>(line, &pos, "alert", true)) + { + m_type = ALERT; + } + // "PARSE" + else if (parser.checkWithArg <special_atom>(line, &pos, "parse", true)) + { + m_type = PARSE; + } + // capability_data + else if ((m_capability_data = parser.get <IMAPParser::capability_data>(line, &pos, true))) + { + m_type = CAPABILITY; + } + // "PERMANENTFLAGS" SPACE flag_list + else if (parser.checkWithArg <special_atom>(line, &pos, "permanentflags", true)) + { + m_type = PERMANENTFLAGS; + + parser.check <SPACE>(line, &pos); + + m_flag_list = parser.get <IMAPParser::flag_list>(line, &pos); + } + // "READ-ONLY" + else if (parser.checkWithArg <special_atom>(line, &pos, "read-only", true)) + { + m_type = READ_ONLY; + } + // "READ-WRITE" + else if (parser.checkWithArg <special_atom>(line, &pos, "read-write", true)) + { + m_type = READ_WRITE; + } + // "TRYCREATE" + else if (parser.checkWithArg <special_atom>(line, &pos, "trycreate", true)) + { + m_type = TRYCREATE; + } + // "UIDVALIDITY" SPACE nz_number + else if (parser.checkWithArg <special_atom>(line, &pos, "uidvalidity", true)) + { + m_type = UIDVALIDITY; + + parser.check <SPACE>(line, &pos); + m_nz_number = parser.get <IMAPParser::nz_number>(line, &pos); + } + // "UIDNEXT" SPACE nz_number + else if (parser.checkWithArg <special_atom>(line, &pos, "uidnext", true)) + { + m_type = UIDNEXT; + + parser.check <SPACE>(line, &pos); + m_nz_number = parser.get <IMAPParser::nz_number>(line, &pos); + } + // "UNSEEN" SPACE nz_number + else if (parser.checkWithArg <special_atom>(line, &pos, "unseen", true)) + { + m_type = UNSEEN; + + parser.check <SPACE>(line, &pos); + m_nz_number = parser.get <IMAPParser::nz_number>(line, &pos); + } + // "HIGHESTMODSEQ" SP mod-sequence-value + else if (parser.checkWithArg <special_atom>(line, &pos, "highestmodseq", true)) + { + m_type = HIGHESTMODSEQ; + + parser.check <SPACE>(line, &pos); + m_mod_sequence_value = parser.get <IMAPParser::mod_sequence_value>(line, &pos); + } + // "NOMODSEQ" + else if (parser.checkWithArg <special_atom>(line, &pos, "nomodseq", true)) + { + m_type = NOMODSEQ; + } + // "MODIFIED" SP sequence-set + else if (parser.checkWithArg <special_atom>(line, &pos, "modified", true)) + { + m_type = MODIFIED; + + parser.check <SPACE>(line, &pos); + + m_sequence_set = parser.get <IMAPParser::sequence_set>(line, &pos); + } + // atom [SPACE 1*<any TEXT_CHAR except "]">] + else + { + m_type = OTHER; + + m_atom = parser.get <IMAPParser::atom>(line, &pos); + + if (parser.check <SPACE>(line, &pos, true)) + m_text = parser.get <text_except <']'> >(line, &pos); + } + + *currentPos = pos; + } + + + enum Type + { + // Extensions + HIGHESTMODSEQ, + NOMODSEQ, + MODIFIED, + + // Standard IMAP + ALERT, + PARSE, + CAPABILITY, + PERMANENTFLAGS, + READ_ONLY, + READ_WRITE, + TRYCREATE, + UIDVALIDITY, + UIDNEXT, + UNSEEN, + OTHER + }; + + private: + + Type m_type; + + IMAPParser::nz_number* m_nz_number; + IMAPParser::atom* m_atom; + IMAPParser::flag_list* m_flag_list; + IMAPParser::text* m_text; + IMAPParser::mod_sequence_value* m_mod_sequence_value; + IMAPParser::sequence_set* m_sequence_set; + IMAPParser::capability_data* m_capability_data; + + public: + + Type type() const { return (m_type); } + + const IMAPParser::nz_number* nz_number() const { return (m_nz_number); } + const IMAPParser::atom* atom() const { return (m_atom); } + const IMAPParser::flag_list* flag_list() const { return (m_flag_list); } + const IMAPParser::text* text() const { return (m_text); } + const IMAPParser::mod_sequence_value* mod_sequence_value() const { return m_mod_sequence_value; } + const IMAPParser::sequence_set* sequence_set() const { return m_sequence_set; } + const IMAPParser::capability_data* capability_data() const { return m_capability_data; } + }; + + + // + // resp_text ::= ["[" resp_text_code "]" SPACE] (text_mime2 / text) + // ;; text SHOULD NOT begin with "[" or "=" + + class resp_text : public component + { + public: + + resp_text() + : m_resp_text_code(NULL) + { + } + + ~resp_text() + { + delete (m_resp_text_code); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("resp_text"); + + size_t pos = *currentPos; + + if (parser.check <one_char <'['> >(line, &pos, true)) + { + m_resp_text_code = parser.get <IMAPParser::resp_text_code>(line, &pos); + + parser.check <one_char <']'> >(line, &pos); + parser.check <SPACE>(line, &pos, true); + } + + text_mime2* text1 = parser.get <text_mime2>(line, &pos, true); + + if (text1 != NULL) + { + m_text = text1->value(); + delete (text1); + } + else + { + IMAPParser::text* text2 = + parser.get <IMAPParser::text>(line, &pos, true); + + if (text2 != NULL) + { + m_text = text2->value(); + delete (text2); + } + else + { + // Empty response text + } + } + + *currentPos = pos; + } + + private: + + IMAPParser::resp_text_code* m_resp_text_code; + string m_text; + + public: + + const IMAPParser::resp_text_code* resp_text_code() const { return (m_resp_text_code); } + const string& text() const { return (m_text); } + }; + + + // + // continue_req ::= "+" SPACE (resp_text / base64) + // + + class continue_req : public component + { + public: + + continue_req() + : m_resp_text(NULL) + { + } + + ~continue_req() + { + delete (m_resp_text); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("continue_req"); + + size_t pos = *currentPos; + + parser.check <one_char <'+'> >(line, &pos); + + if (!parser.isStrict()) + { + // Some servers do not send SPACE when response text is empty + if (parser.check <SPACE>(line, &pos, true)) + m_resp_text = parser.get <IMAPParser::resp_text>(line, &pos); + else + m_resp_text = new IMAPParser::resp_text(); // empty + } + else + { + parser.check <SPACE>(line, &pos); + + m_resp_text = parser.get <IMAPParser::resp_text>(line, &pos); + } + + parser.check <CRLF>(line, &pos); + + *currentPos = pos; + } + + private: + + IMAPParser::resp_text* m_resp_text; + + public: + + const IMAPParser::resp_text* resp_text() const { return (m_resp_text); } + }; + + + // + // resp_cond_state ::= ("OK" / "NO" / "BAD") SPACE resp_text + // ;; Status condition + // + + class resp_cond_state : public component + { + public: + + resp_cond_state() + : m_resp_text(NULL), m_status(BAD) + { + } + + ~resp_cond_state() + { + delete (m_resp_text); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("resp_cond_state"); + + size_t pos = *currentPos; + + if (parser.checkWithArg <special_atom>(line, &pos, "ok", true)) + { + m_status = OK; + } + else if (parser.checkWithArg <special_atom>(line, &pos, "no", true)) + { + m_status = NO; + } + else + { + parser.checkWithArg <special_atom>(line, &pos, "bad"); + m_status = BAD; + } + + parser.check <SPACE>(line, &pos); + + m_resp_text = parser.get <IMAPParser::resp_text>(line, &pos); + + *currentPos = pos; + } + + + enum Status + { + OK, + NO, + BAD + }; + + private: + + IMAPParser::resp_text* m_resp_text; + Status m_status; + + public: + + const IMAPParser::resp_text* resp_text() const { return (m_resp_text); } + Status status() const { return (m_status); } + }; + + + // + // resp_cond_bye ::= "BYE" SPACE resp_text + // + + class resp_cond_bye : public component + { + public: + + resp_cond_bye() + : m_resp_text(NULL) + { + } + + ~resp_cond_bye() + { + delete (m_resp_text); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("resp_cond_bye"); + + size_t pos = *currentPos; + + parser.checkWithArg <special_atom>(line, &pos, "bye"); + + parser.check <SPACE>(line, &pos); + + m_resp_text = parser.get <IMAPParser::resp_text>(line, &pos); + + *currentPos = pos; + } + + private: + + IMAPParser::resp_text* m_resp_text; + + public: + + const IMAPParser::resp_text* resp_text() const { return (m_resp_text); } + }; + + + // + // resp_cond_auth ::= ("OK" / "PREAUTH") SPACE resp_text + // ;; Authentication condition + // + + class resp_cond_auth : public component + { + public: + + resp_cond_auth() + : m_resp_text(NULL) + { + } + + ~resp_cond_auth() + { + delete (m_resp_text); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("resp_cond_auth"); + + size_t pos = *currentPos; + + if (parser.checkWithArg <special_atom>(line, &pos, "ok", true)) + { + m_cond = OK; + } + else + { + parser.checkWithArg <special_atom>(line, &pos, "preauth"); + + m_cond = PREAUTH; + } + + parser.check <SPACE>(line, &pos); + + m_resp_text = parser.get <IMAPParser::resp_text>(line, &pos); + + *currentPos = pos; + } + + + enum Condition + { + OK, + PREAUTH + }; + + private: + + Condition m_cond; + IMAPParser::resp_text* m_resp_text; + + public: + + Condition condition() const { return (m_cond); } + const IMAPParser::resp_text* resp_text() const { return (m_resp_text); } + }; + + + // + // mailbox_data ::= "FLAGS" SPACE mailbox_flag_list / + // "LIST" SPACE mailbox_list / + // "LSUB" SPACE mailbox_list / + // "MAILBOX" SPACE text / + // "SEARCH" [SPACE 1#nz_number] / + // "STATUS" SPACE mailbox SPACE + // "(" [status-att-list] ")" / + // number SPACE "EXISTS" / + // number SPACE "RECENT" + // + + class mailbox_data : public component + { + public: + + mailbox_data() + : m_number(NULL), m_mailbox_flag_list(NULL), m_mailbox_list(NULL), + m_mailbox(NULL), m_text(NULL), m_status_att_list(NULL) + { + } + + ~mailbox_data() + { + delete (m_number); + delete (m_mailbox_flag_list); + delete (m_mailbox_list); + delete (m_mailbox); + delete (m_text); + + for (std::vector <nz_number*>::iterator it = m_search_nz_number_list.begin() ; + it != m_search_nz_number_list.end() ; ++it) + { + delete (*it); + } + + delete m_status_att_list; + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("mailbox_data"); + + size_t pos = *currentPos; + + m_number = parser.get <IMAPParser::number>(line, &pos, true); + + if (m_number) + { + parser.check <SPACE>(line, &pos); + + if (parser.checkWithArg <special_atom>(line, &pos, "exists", true)) + { + m_type = EXISTS; + } + else + { + parser.checkWithArg <special_atom>(line, &pos, "recent"); + + m_type = RECENT; + } + } + else + { + // "FLAGS" SPACE mailbox_flag_list + if (parser.checkWithArg <special_atom>(line, &pos, "flags", true)) + { + parser.check <SPACE>(line, &pos); + + m_mailbox_flag_list = parser.get <IMAPParser::mailbox_flag_list>(line, &pos); + + m_type = FLAGS; + } + // "LIST" SPACE mailbox_list + else if (parser.checkWithArg <special_atom>(line, &pos, "list", true)) + { + parser.check <SPACE>(line, &pos); + + m_mailbox_list = parser.get <IMAPParser::mailbox_list>(line, &pos); + + m_type = LIST; + } + // "LSUB" SPACE mailbox_list + else if (parser.checkWithArg <special_atom>(line, &pos, "lsub", true)) + { + parser.check <SPACE>(line, &pos); + + m_mailbox_list = parser.get <IMAPParser::mailbox_list>(line, &pos); + + m_type = LSUB; + } + // "MAILBOX" SPACE text + else if (parser.checkWithArg <special_atom>(line, &pos, "mailbox", true)) + { + parser.check <SPACE>(line, &pos); + + m_text = parser.get <IMAPParser::text>(line, &pos); + + m_type = MAILBOX; + } + // "SEARCH" [SPACE 1#nz_number] + else if (parser.checkWithArg <special_atom>(line, &pos, "search", true)) + { + if (parser.check <SPACE>(line, &pos, true)) + { + m_search_nz_number_list.push_back + (parser.get <nz_number>(line, &pos)); + + while (parser.check <SPACE>(line, &pos, true)) + { + m_search_nz_number_list.push_back + (parser.get <nz_number>(line, &pos)); + } + } + + m_type = SEARCH; + } + // "STATUS" SPACE mailbox SPACE + // "(" [status_att_list] ")" + else + { + parser.checkWithArg <special_atom>(line, &pos, "status"); + parser.check <SPACE>(line, &pos); + + m_mailbox = parser.get <IMAPParser::mailbox>(line, &pos); + + parser.check <SPACE>(line, &pos); + + parser.check <one_char <'('> >(line, &pos); + + m_status_att_list = parser.get <IMAPParser::status_att_list>(line, &pos, true); + + parser.check <one_char <')'> >(line, &pos); + + m_type = STATUS; + } + } + + *currentPos = pos; + } + + + enum Type + { + FLAGS, + LIST, + LSUB, + MAILBOX, + SEARCH, + STATUS, + EXISTS, + RECENT + }; + + private: + + Type m_type; + + IMAPParser::number* m_number; + IMAPParser::mailbox_flag_list* m_mailbox_flag_list; + IMAPParser::mailbox_list* m_mailbox_list; + IMAPParser::mailbox* m_mailbox; + IMAPParser::text* m_text; + std::vector <nz_number*> m_search_nz_number_list; + IMAPParser::status_att_list* m_status_att_list; + + public: + + Type type() const { return (m_type); } + + const IMAPParser::number* number() const { return (m_number); } + const IMAPParser::mailbox_flag_list* mailbox_flag_list() const { return (m_mailbox_flag_list); } + const IMAPParser::mailbox_list* mailbox_list() const { return (m_mailbox_list); } + const IMAPParser::mailbox* mailbox() const { return (m_mailbox); } + const IMAPParser::text* text() const { return (m_text); } + const std::vector <nz_number*>& search_nz_number_list() const { return (m_search_nz_number_list); } + const IMAPParser::status_att_list* status_att_list() const { return m_status_att_list; } + }; + + + // + // response_data ::= "*" SPACE (resp_cond_state / resp_cond_bye / + // mailbox_data / message_data / capability_data) CRLF + // + + class response_data : public component + { + public: + + response_data() + : m_resp_cond_state(NULL), m_resp_cond_bye(NULL), + m_mailbox_data(NULL), m_message_data(NULL), m_capability_data(NULL) + { + } + + ~response_data() + { + delete (m_resp_cond_state); + delete (m_resp_cond_bye); + delete (m_mailbox_data); + delete (m_message_data); + delete (m_capability_data); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("response_data"); + + size_t pos = *currentPos; + + parser.check <one_char <'*'> >(line, &pos); + parser.check <SPACE>(line, &pos); + + if (!(m_resp_cond_state = parser.get <IMAPParser::resp_cond_state>(line, &pos, true))) + if (!(m_resp_cond_bye = parser.get <IMAPParser::resp_cond_bye>(line, &pos, true))) + if (!(m_mailbox_data = parser.get <IMAPParser::mailbox_data>(line, &pos, true))) + if (!(m_message_data = parser.get <IMAPParser::message_data>(line, &pos, true))) + m_capability_data = parser.get <IMAPParser::capability_data>(line, &pos); + + if (!parser.isStrict()) + { + // Allow SPACEs at end of line + while (parser.check <SPACE>(line, &pos, /* noThrow */ true)) + ; + } + + parser.check <CRLF>(line, &pos); + + *currentPos = pos; + } + + private: + + IMAPParser::resp_cond_state* m_resp_cond_state; + IMAPParser::resp_cond_bye* m_resp_cond_bye; + IMAPParser::mailbox_data* m_mailbox_data; + IMAPParser::message_data* m_message_data; + IMAPParser::capability_data* m_capability_data; + + public: + + const IMAPParser::resp_cond_state* resp_cond_state() const { return (m_resp_cond_state); } + const IMAPParser::resp_cond_bye* resp_cond_bye() const { return (m_resp_cond_bye); } + const IMAPParser::mailbox_data* mailbox_data() const { return (m_mailbox_data); } + const IMAPParser::message_data* message_data() const { return (m_message_data); } + const IMAPParser::capability_data* capability_data() const { return (m_capability_data); } + }; + + + class continue_req_or_response_data : public component + { + public: + + continue_req_or_response_data() + : m_continue_req(NULL), m_response_data(NULL) + { + } + + ~continue_req_or_response_data() + { + delete (m_continue_req); + delete (m_response_data); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("continue_req_or_response_data"); + + size_t pos = *currentPos; + + if (!(m_continue_req = parser.get <IMAPParser::continue_req>(line, &pos, true))) + m_response_data = parser.get <IMAPParser::response_data>(line, &pos); + + *currentPos = pos; + } + + private: + + IMAPParser::continue_req* m_continue_req; + IMAPParser::response_data* m_response_data; + + public: + + const IMAPParser::continue_req* continue_req() const { return (m_continue_req); } + const IMAPParser::response_data* response_data() const { return (m_response_data); } + }; + + + // + // response_fatal ::= "*" SPACE resp_cond_bye CRLF + // ;; Server closes connection immediately + // + + class response_fatal : public component + { + public: + + response_fatal() + : m_resp_cond_bye(NULL) + { + } + + ~response_fatal() + { + delete (m_resp_cond_bye); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("response_fatal"); + + size_t pos = *currentPos; + + parser.check <one_char <'*'> >(line, &pos); + parser.check <SPACE>(line, &pos); + + m_resp_cond_bye = parser.get <IMAPParser::resp_cond_bye>(line, &pos); + + if (!parser.isStrict()) + { + // Allow SPACEs at end of line + while (parser.check <SPACE>(line, &pos, /* noThrow */ true)) + ; + } + + parser.check <CRLF>(line, &pos); + + *currentPos = pos; + } + + private: + + IMAPParser::resp_cond_bye* m_resp_cond_bye; + + public: + + const IMAPParser::resp_cond_bye* resp_cond_bye() const { return (m_resp_cond_bye); } + }; + + + // + // response_tagged ::= tag SPACE resp_cond_state CRLF + // + + class response_tagged : public component + { + public: + + response_tagged() + : m_resp_cond_state(NULL) + { + } + + ~response_tagged() + { + delete (m_resp_cond_state); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("response_tagged"); + + size_t pos = *currentPos; + + parser.check <IMAPParser::xtag>(line, &pos); + parser.check <SPACE>(line, &pos); + m_resp_cond_state = parser.get <IMAPParser::resp_cond_state>(line, &pos); + + if (!parser.isStrict()) + { + // Allow SPACEs at end of line + while (parser.check <SPACE>(line, &pos, /* noThrow */ true)) + ; + } + + parser.check <CRLF>(line, &pos); + + *currentPos = pos; + } + + private: + + IMAPParser::resp_cond_state* m_resp_cond_state; + + public: + + const IMAPParser::resp_cond_state* resp_cond_state() const { return (m_resp_cond_state); } + }; + + + // + // response_done ::= response_tagged / response_fatal + // + + class response_done : public component + { + public: + + response_done() + : m_response_tagged(NULL), m_response_fatal(NULL) + { + } + + ~response_done() + { + delete (m_response_tagged); + delete (m_response_fatal); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("response_done"); + + size_t pos = *currentPos; + + if (!(m_response_tagged = parser.get <IMAPParser::response_tagged>(line, &pos, true))) + m_response_fatal = parser.get <IMAPParser::response_fatal>(line, &pos); + + *currentPos = pos; + } + + private: + + IMAPParser::response_tagged* m_response_tagged; + IMAPParser::response_fatal* m_response_fatal; + + public: + + const IMAPParser::response_tagged* response_tagged() const { return (m_response_tagged); } + const IMAPParser::response_fatal* response_fatal() const { return (m_response_fatal); } + }; + + + // + // response ::= *(continue_req / response_data) response_done + // + + class response : public component + { + public: + + response() + : m_response_done(NULL) + { + } + + ~response() + { + for (std::vector <IMAPParser::continue_req_or_response_data*>::iterator + it = m_continue_req_or_response_data.begin() ; + it != m_continue_req_or_response_data.end() ; ++it) + { + delete (*it); + } + + delete (m_response_done); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("response"); + + size_t pos = *currentPos; + string curLine = line; + bool partial = false; // partial response + + IMAPParser::continue_req_or_response_data* resp = NULL; + + while ((resp = parser.get <IMAPParser::continue_req_or_response_data>(curLine, &pos, true)) != NULL) + { + m_continue_req_or_response_data.push_back(resp); + + // Partial response (continue_req) + if (resp->continue_req()) + { + partial = true; + break; + } + + // We have read a CRLF, read another line + curLine = parser.readLine(); + pos = 0; + } + + if (!partial) + m_response_done = parser.get <IMAPParser::response_done>(curLine, &pos); + + *currentPos = pos; + } + + + bool isBad() const + { + if (!response_done()) // incomplete (partial) response + return (true); + + if (response_done()->response_fatal()) + return (true); + + if (response_done()->response_tagged()->resp_cond_state()-> + status() == IMAPParser::resp_cond_state::BAD) + { + return (true); + } + + return (false); + } + + void setErrorLog(const string& errorLog) + { + m_errorLog = errorLog; + } + + const string& getErrorLog() const + { + return m_errorLog; + } + + private: + + std::vector <IMAPParser::continue_req_or_response_data*> m_continue_req_or_response_data; + IMAPParser::response_done* m_response_done; + + string m_errorLog; + + public: + + const std::vector <IMAPParser::continue_req_or_response_data*>& continue_req_or_response_data() const { return (m_continue_req_or_response_data); } + const IMAPParser::response_done* response_done() const { return (m_response_done); } + }; + + + // + // greeting ::= "*" SPACE (resp_cond_auth / resp_cond_bye) CRLF + // + + class greeting : public component + { + public: + + greeting() + : m_resp_cond_auth(NULL), m_resp_cond_bye(NULL) + { + } + + ~greeting() + { + delete (m_resp_cond_auth); + delete (m_resp_cond_bye); + } + + void go(IMAPParser& parser, string& line, size_t* currentPos) + { + DEBUG_ENTER_COMPONENT("greeting"); + + size_t pos = *currentPos; + + parser.check <one_char <'*'> >(line, &pos); + parser.check <SPACE>(line, &pos); + + if (!(m_resp_cond_auth = parser.get <IMAPParser::resp_cond_auth>(line, &pos, true))) + m_resp_cond_bye = parser.get <IMAPParser::resp_cond_bye>(line, &pos); + + parser.check <CRLF>(line, &pos); + + *currentPos = pos; + } + + void setErrorLog(const string& errorLog) + { + m_errorLog = errorLog; + } + + const string& getErrorLog() const + { + return m_errorLog; + } + + private: + + IMAPParser::resp_cond_auth* m_resp_cond_auth; + IMAPParser::resp_cond_bye* m_resp_cond_bye; + + string m_errorLog; + + public: + + const IMAPParser::resp_cond_auth* resp_cond_auth() const { return (m_resp_cond_auth); } + const IMAPParser::resp_cond_bye* resp_cond_bye() const { return (m_resp_cond_bye); } + }; + + + + // + // The main functions used to parse a response + // + + response* readResponse(literalHandler* lh = NULL) + { + size_t pos = 0; + string line = readLine(); + + m_literalHandler = lh; + response* resp = get <response>(line, &pos); + m_literalHandler = NULL; + + resp->setErrorLog(lastLine()); + + return (resp); + } + + + greeting* readGreeting() + { + size_t pos = 0; + string line = readLine(); + + greeting* greet = get <greeting>(line, &pos); + + greet->setErrorLog(lastLine()); + + return greet; + } + + + // + // Get a token and advance + // + + template <class TYPE> + TYPE* get(string& line, size_t* currentPos, + const bool noThrow = false) + { + component* resp = new TYPE; + return internalGet <TYPE>(resp, line, currentPos, noThrow); + } + + + template <class TYPE, class ARG1_TYPE, class ARG2_TYPE> + TYPE* getWithArgs(string& line, size_t* currentPos, + ARG1_TYPE arg1, ARG2_TYPE arg2, const bool noThrow = false) + { + component* resp = new TYPE(arg1, arg2); + return internalGet <TYPE>(resp, line, currentPos, noThrow); + } + + +private: + + template <class TYPE> + TYPE* internalGet(component* resp, string& line, size_t* currentPos, + const bool noThrow = false) + { + const size_t oldPos = *currentPos; + + try + { + resp->go(*this, line, currentPos); + } + catch (exceptions::operation_timed_out&) + { + // Always rethrow + throw; + } + catch (exception&) + { + *currentPos = oldPos; + + delete (resp); + if (!noThrow) throw; + return (NULL); + } + + return static_cast <TYPE*>(resp); + } + + const string lastLine() const + { + // Remove blanks and new lines at the end of the line. + string line(m_lastLine); + + string::const_iterator it = line.end(); + int count = 0; + + while (it != line.begin()) + { + const unsigned char c = *(it - 1); + + if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r')) + break; + + ++count; + --it; + } + + line.resize(line.length() - count); + + return (line); + } + +public: + + // + // Check a token and advance + // + + template <class TYPE> + bool check(string& line, size_t* currentPos, + const bool noThrow = false) + { + const size_t oldPos = *currentPos; + + try + { + TYPE term; + term.go(*this, line, currentPos); + } + catch (exceptions::operation_timed_out&) + { + // Always rethrow + throw; + } + catch (exception&) + { + *currentPos = oldPos; + + if (!noThrow) throw; + return false; + } + + return true; + } + + template <class TYPE, class ARG_TYPE> + bool checkWithArg(string& line, size_t* currentPos, + const ARG_TYPE arg, const bool noThrow = false) + { + const size_t oldPos = *currentPos; + + try + { + TYPE term(arg); + term.go(*this, line, currentPos); + } + catch (exceptions::operation_timed_out&) + { + // Always rethrow + throw; + } + catch (exception&) + { + *currentPos = oldPos; + + if (!noThrow) throw; + return false; + } + + return true; + } + + +private: + + weak_ptr <IMAPTag> m_tag; + weak_ptr <socket> m_socket; + + utility::progressListener* m_progress; + + bool m_strict; + + literalHandler* m_literalHandler; + + weak_ptr <timeoutHandler> m_timeoutHandler; + + + string m_buffer; + + string m_lastLine; + +public: + + // + // Read one line + // + + const string readLine() + { + size_t pos; + + while ((pos = m_buffer.find('\n')) == string::npos) + { + read(); + } + + string line; + line.resize(pos + 1); + std::copy(m_buffer.begin(), m_buffer.begin() + pos + 1, line.begin()); + + m_buffer.erase(m_buffer.begin(), m_buffer.begin() + pos + 1); + + m_lastLine = line; + +#if DEBUG_RESPONSE + std::cout << std::endl << "Read line:" << std::endl << line << std::endl; +#endif + + return (line); + } + + + // + // Read available data from socket stream + // + + void read() + { + string receiveBuffer; + + shared_ptr <timeoutHandler> toh = m_timeoutHandler.lock(); + shared_ptr <socket> sok = m_socket.lock(); + + if (toh) + toh->resetTimeOut(); + + while (receiveBuffer.empty()) + { + // Check whether the time-out delay is elapsed + if (toh && toh->isTimeOut()) + { + if (!toh->handleTimeOut()) + throw exceptions::operation_timed_out(); + } + + // We have received data: reset the time-out counter + sok->receive(receiveBuffer); + + if (receiveBuffer.empty()) // buffer is empty + { + platform::getHandler()->wait(); + continue; + } + + // We have received data ... + if (toh) + toh->resetTimeOut(); + } + + m_buffer += receiveBuffer; + } + + + void readLiteral(literalHandler::target& buffer, size_t count) + { + size_t len = 0; + string receiveBuffer; + + shared_ptr <timeoutHandler> toh = m_timeoutHandler.lock(); + shared_ptr <socket> sok = m_socket.lock(); + + if (m_progress) + m_progress->start(count); + + if (toh) + toh->resetTimeOut(); + + if (!m_buffer.empty()) + { + if (m_buffer.length() > count) + { + buffer.putData(string(m_buffer.begin(), m_buffer.begin() + count)); + m_buffer.erase(m_buffer.begin(), m_buffer.begin() + count); + len = count; + } + else + { + len += m_buffer.length(); + buffer.putData(m_buffer); + m_buffer.clear(); + } + } + + while (len < count) + { + // Check whether the time-out delay is elapsed + if (toh && toh->isTimeOut()) + { + if (!toh->handleTimeOut()) + throw exceptions::operation_timed_out(); + + toh->resetTimeOut(); + } + + // Receive data from the socket + sok->receive(receiveBuffer); + + if (receiveBuffer.empty()) // buffer is empty + { + platform::getHandler()->wait(); + continue; + } + + // We have received data: reset the time-out counter + if (toh) + toh->resetTimeOut(); + + if (len + receiveBuffer.length() > count) + { + const size_t remaining = count - len; + + // Get the needed amount of data + buffer.putData(string(receiveBuffer.begin(), receiveBuffer.begin() + remaining)); + + // Put the remaining data into the internal response buffer + receiveBuffer.erase(receiveBuffer.begin(), receiveBuffer.begin() + remaining); + m_buffer += receiveBuffer; + + len = count; + } + else + { + buffer.putData(receiveBuffer); + len += receiveBuffer.length(); + } + + // Notify progress + if (m_progress) + m_progress->progress(len, count); + } + + if (m_progress) + m_progress->stop(count); + } +}; + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + +#endif // VMIME_NET_IMAP_IMAPPARSER_HPP_INCLUDED diff --git a/src/vmime/net/imap/IMAPSStore.cpp b/src/vmime/net/imap/IMAPSStore.cpp new file mode 100644 index 00000000..c9e64f5b --- /dev/null +++ b/src/vmime/net/imap/IMAPSStore.cpp @@ -0,0 +1,79 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include "vmime/net/imap/IMAPSStore.hpp" + + +namespace vmime { +namespace net { +namespace imap { + + +IMAPSStore::IMAPSStore(shared_ptr <session> sess, shared_ptr <security::authenticator> auth) + : IMAPStore(sess, auth, true) +{ +} + + +IMAPSStore::~IMAPSStore() +{ +} + + +const string IMAPSStore::getProtocolName() const +{ + return "imaps"; +} + + + +// Service infos + +IMAPServiceInfos IMAPSStore::sm_infos(true); + + +const serviceInfos& IMAPSStore::getInfosInstance() +{ + return sm_infos; +} + + +const serviceInfos& IMAPSStore::getInfos() const +{ + return sm_infos; +} + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + diff --git a/src/vmime/net/imap/IMAPSStore.hpp b/src/vmime/net/imap/IMAPSStore.hpp new file mode 100644 index 00000000..9d27bdd0 --- /dev/null +++ b/src/vmime/net/imap/IMAPSStore.hpp @@ -0,0 +1,71 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_IMAP_IMAPSSTORE_HPP_INCLUDED +#define VMIME_NET_IMAP_IMAPSSTORE_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include "vmime/net/imap/IMAPStore.hpp" + + +namespace vmime { +namespace net { +namespace imap { + + +/** IMAPS store service. + */ + +class VMIME_EXPORT IMAPSStore : public IMAPStore +{ +public: + + IMAPSStore(shared_ptr <session> sess, shared_ptr <security::authenticator> auth); + ~IMAPSStore(); + + const string getProtocolName() const; + + static const serviceInfos& getInfosInstance(); + const serviceInfos& getInfos() const; + +private: + + static IMAPServiceInfos sm_infos; +}; + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + +#endif // VMIME_NET_IMAP_IMAPSSTORE_HPP_INCLUDED + diff --git a/src/vmime/net/imap/IMAPServiceInfos.cpp b/src/vmime/net/imap/IMAPServiceInfos.cpp new file mode 100644 index 00000000..46dbc2e1 --- /dev/null +++ b/src/vmime/net/imap/IMAPServiceInfos.cpp @@ -0,0 +1,137 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include "vmime/net/imap/IMAPServiceInfos.hpp" + + +namespace vmime { +namespace net { +namespace imap { + + +IMAPServiceInfos::IMAPServiceInfos(const bool imaps) + : m_imaps(imaps) +{ +} + + +const string IMAPServiceInfos::getPropertyPrefix() const +{ + if (m_imaps) + return "store.imaps."; + else + return "store.imap."; +} + + +const IMAPServiceInfos::props& IMAPServiceInfos::getProperties() const +{ + static props imapProps = + { + // IMAP-specific options +#if VMIME_HAVE_SASL_SUPPORT + property("options.sasl", serviceInfos::property::TYPE_BOOLEAN, "true"), + property("options.sasl.fallback", serviceInfos::property::TYPE_BOOLEAN, "true"), +#endif // VMIME_HAVE_SASL_SUPPORT + + // Common properties + property(serviceInfos::property::AUTH_USERNAME, serviceInfos::property::FLAG_REQUIRED), + property(serviceInfos::property::AUTH_PASSWORD, serviceInfos::property::FLAG_REQUIRED), + +#if VMIME_HAVE_TLS_SUPPORT + property(serviceInfos::property::CONNECTION_TLS), + property(serviceInfos::property::CONNECTION_TLS_REQUIRED), +#endif // VMIME_HAVE_TLS_SUPPORT + + property(serviceInfos::property::SERVER_ADDRESS, serviceInfos::property::FLAG_REQUIRED), + property(serviceInfos::property::SERVER_PORT, "143"), + }; + + static props imapsProps = + { + // IMAP-specific options +#if VMIME_HAVE_SASL_SUPPORT + property("options.sasl", serviceInfos::property::TYPE_BOOLEAN, "true"), + property("options.sasl.fallback", serviceInfos::property::TYPE_BOOLEAN, "true"), +#endif // VMIME_HAVE_SASL_SUPPORT + + // Common properties + property(serviceInfos::property::AUTH_USERNAME, serviceInfos::property::FLAG_REQUIRED), + property(serviceInfos::property::AUTH_PASSWORD, serviceInfos::property::FLAG_REQUIRED), + +#if VMIME_HAVE_TLS_SUPPORT + property(serviceInfos::property::CONNECTION_TLS), + property(serviceInfos::property::CONNECTION_TLS_REQUIRED), +#endif // VMIME_HAVE_TLS_SUPPORT + + property(serviceInfos::property::SERVER_ADDRESS, serviceInfos::property::FLAG_REQUIRED), + property(serviceInfos::property::SERVER_PORT, "993"), + }; + + return m_imaps ? imapsProps : imapProps; +} + + +const std::vector <serviceInfos::property> IMAPServiceInfos::getAvailableProperties() const +{ + std::vector <property> list; + const props& p = getProperties(); + + // IMAP-specific options +#if VMIME_HAVE_SASL_SUPPORT + list.push_back(p.PROPERTY_OPTIONS_SASL); + list.push_back(p.PROPERTY_OPTIONS_SASL_FALLBACK); +#endif // VMIME_HAVE_SASL_SUPPORT + + // Common properties + list.push_back(p.PROPERTY_AUTH_USERNAME); + list.push_back(p.PROPERTY_AUTH_PASSWORD); + +#if VMIME_HAVE_TLS_SUPPORT + if (!m_imaps) + { + list.push_back(p.PROPERTY_CONNECTION_TLS); + list.push_back(p.PROPERTY_CONNECTION_TLS_REQUIRED); + } +#endif // VMIME_HAVE_TLS_SUPPORT + + list.push_back(p.PROPERTY_SERVER_ADDRESS); + list.push_back(p.PROPERTY_SERVER_PORT); + + return list; +} + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + diff --git a/src/vmime/net/imap/IMAPServiceInfos.hpp b/src/vmime/net/imap/IMAPServiceInfos.hpp new file mode 100644 index 00000000..376f4476 --- /dev/null +++ b/src/vmime/net/imap/IMAPServiceInfos.hpp @@ -0,0 +1,91 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_IMAP_IMAPSERVICEINFOS_HPP_INCLUDED +#define VMIME_NET_IMAP_IMAPSERVICEINFOS_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include "vmime/net/serviceInfos.hpp" + + +namespace vmime { +namespace net { +namespace imap { + + +/** Information about IMAP service. + */ + +class VMIME_EXPORT IMAPServiceInfos : public serviceInfos +{ +public: + + IMAPServiceInfos(const bool imaps); + + struct props + { + // IMAP-specific options +#if VMIME_HAVE_SASL_SUPPORT + serviceInfos::property PROPERTY_OPTIONS_SASL; + serviceInfos::property PROPERTY_OPTIONS_SASL_FALLBACK; +#endif // VMIME_HAVE_SASL_SUPPORT + + // Common properties + serviceInfos::property PROPERTY_AUTH_USERNAME; + serviceInfos::property PROPERTY_AUTH_PASSWORD; + +#if VMIME_HAVE_TLS_SUPPORT + serviceInfos::property PROPERTY_CONNECTION_TLS; + serviceInfos::property PROPERTY_CONNECTION_TLS_REQUIRED; +#endif // VMIME_HAVE_TLS_SUPPORT + + serviceInfos::property PROPERTY_SERVER_ADDRESS; + serviceInfos::property PROPERTY_SERVER_PORT; + }; + + const props& getProperties() const; + + const string getPropertyPrefix() const; + const std::vector <serviceInfos::property> getAvailableProperties() const; + +private: + + const bool m_imaps; +}; + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + +#endif // VMIME_NET_IMAP_IMAPSERVICEINFOS_HPP_INCLUDED + diff --git a/src/vmime/net/imap/IMAPStore.cpp b/src/vmime/net/imap/IMAPStore.cpp new file mode 100644 index 00000000..a1a8c9ca --- /dev/null +++ b/src/vmime/net/imap/IMAPStore.cpp @@ -0,0 +1,267 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include "vmime/net/imap/IMAPStore.hpp" +#include "vmime/net/imap/IMAPFolder.hpp" +#include "vmime/net/imap/IMAPConnection.hpp" +#include "vmime/net/imap/IMAPFolderStatus.hpp" + +#include "vmime/exception.hpp" +#include "vmime/platform.hpp" + +#include <map> + + +namespace vmime { +namespace net { +namespace imap { + + +IMAPStore::IMAPStore(shared_ptr <session> sess, shared_ptr <security::authenticator> auth, const bool secured) + : store(sess, getInfosInstance(), auth), m_connection(null), m_isIMAPS(secured) +{ +} + + +IMAPStore::~IMAPStore() +{ + try + { + if (isConnected()) + disconnect(); + } + catch (vmime::exception&) + { + // Ignore + } +} + + +const string IMAPStore::getProtocolName() const +{ + return "imap"; +} + + +shared_ptr <folder> IMAPStore::getRootFolder() +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return make_shared <IMAPFolder> + (folder::path(), + dynamicCast <IMAPStore>(shared_from_this())); +} + + +shared_ptr <folder> IMAPStore::getDefaultFolder() +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return make_shared <IMAPFolder> + (folder::path::component("INBOX"), + dynamicCast <IMAPStore>(shared_from_this())); +} + + +shared_ptr <folder> IMAPStore::getFolder(const folder::path& path) +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return make_shared <IMAPFolder> + (path, dynamicCast <IMAPStore>(shared_from_this())); +} + + +bool IMAPStore::isValidFolderName(const folder::path::component& /* name */) const +{ + return true; +} + + +void IMAPStore::connect() +{ + if (isConnected()) + throw exceptions::already_connected(); + + m_connection = make_shared <IMAPConnection> + (dynamicCast <IMAPStore>(shared_from_this()), getAuthenticator()); + + try + { + m_connection->connect(); + } + catch (std::exception&) + { + m_connection = null; + throw; + } +} + + +bool IMAPStore::isConnected() const +{ + return (m_connection && m_connection->isConnected()); +} + + +bool IMAPStore::isIMAPS() const +{ + return m_isIMAPS; +} + + +bool IMAPStore::isSecuredConnection() const +{ + if (m_connection == NULL) + return false; + + return m_connection->isSecuredConnection(); +} + + +shared_ptr <connectionInfos> IMAPStore::getConnectionInfos() const +{ + if (m_connection == NULL) + return null; + + return m_connection->getConnectionInfos(); +} + + +shared_ptr <IMAPConnection> IMAPStore::getConnection() +{ + return m_connection; +} + + +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_connection = null; +} + + +void IMAPStore::noop() +{ + if (!isConnected()) + throw exceptions::not_connected(); + + m_connection->send(true, "NOOP", true); + + std::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", resp->getErrorLog()); + } + + + for (std::list <IMAPFolder*>::iterator it = m_folders.begin() ; + it != m_folders.end() ; ++it) + { + if ((*it)->isOpen()) + (*it)->noop(); + } +} + + +shared_ptr <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); +} + + +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 + +IMAPServiceInfos IMAPStore::sm_infos(false); + + +const serviceInfos& IMAPStore::getInfosInstance() +{ + return sm_infos; +} + + +const serviceInfos& IMAPStore::getInfos() const +{ + return sm_infos; +} + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + diff --git a/src/vmime/net/imap/IMAPStore.hpp b/src/vmime/net/imap/IMAPStore.hpp new file mode 100644 index 00000000..f854fadf --- /dev/null +++ b/src/vmime/net/imap/IMAPStore.hpp @@ -0,0 +1,120 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_IMAP_IMAPSTORE_HPP_INCLUDED +#define VMIME_NET_IMAP_IMAPSTORE_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include "vmime/net/store.hpp" +#include "vmime/net/socket.hpp" +#include "vmime/net/folder.hpp" + +#include "vmime/net/imap/IMAPServiceInfos.hpp" +#include "vmime/net/imap/IMAPConnection.hpp" + + +namespace vmime { +namespace net { +namespace imap { + + +class IMAPParser; +class IMAPTag; +class IMAPFolder; + + +/** IMAP store service. + */ + +class VMIME_EXPORT IMAPStore : public store +{ + friend class IMAPFolder; + friend class IMAPMessage; + friend class IMAPConnection; + +public: + + IMAPStore(shared_ptr <session> sess, shared_ptr <security::authenticator> auth, const bool secured = false); + ~IMAPStore(); + + const string getProtocolName() const; + + shared_ptr <folder> getDefaultFolder(); + shared_ptr <folder> getRootFolder(); + shared_ptr <folder> getFolder(const folder::path& path); + + bool isValidFolderName(const folder::path::component& name) const; + + static const serviceInfos& getInfosInstance(); + const serviceInfos& getInfos() const; + + void connect(); + bool isConnected() const; + void disconnect(); + + void noop(); + + int getCapabilities() const; + + bool isIMAPS() const; + + bool isSecuredConnection() const; + shared_ptr <connectionInfos> getConnectionInfos() const; + shared_ptr <IMAPConnection> getConnection(); + +protected: + + // Connection + shared_ptr <IMAPConnection> m_connection; + + + + shared_ptr <IMAPConnection> connection(); + + + void registerFolder(IMAPFolder* folder); + void unregisterFolder(IMAPFolder* folder); + + std::list <IMAPFolder*> m_folders; + + const bool m_isIMAPS; // Use IMAPS + + + static IMAPServiceInfos sm_infos; +}; + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + +#endif // VMIME_NET_IMAP_IMAPSTORE_HPP_INCLUDED diff --git a/src/vmime/net/imap/IMAPTag.cpp b/src/vmime/net/imap/IMAPTag.cpp new file mode 100644 index 00000000..14d12788 --- /dev/null +++ b/src/vmime/net/imap/IMAPTag.cpp @@ -0,0 +1,122 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include "vmime/net/imap/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); + generate(); +} + + +IMAPTag::IMAPTag(const IMAPTag& tag) + : object(), m_number(tag.m_number) +{ + m_tag.resize(4); + generate(); +} + + +IMAPTag::IMAPTag() + : m_number(1) +{ + m_tag.resize(4); + generate(); +} + + +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); +} + + +int IMAPTag::maximumNumber() const +{ + return sm_maxNumber - 1; +} + + +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] = static_cast <char>('0' + (m_number % 1000) / 100); + m_tag[2] = static_cast <char>('0' + (m_number % 100) / 10); + m_tag[3] = static_cast <char>('0' + m_number % 10); +} + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + diff --git a/src/vmime/net/imap/IMAPTag.hpp b/src/vmime/net/imap/IMAPTag.hpp new file mode 100644 index 00000000..430a3b10 --- /dev/null +++ b/src/vmime/net/imap/IMAPTag.hpp @@ -0,0 +1,79 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_IMAP_IMAPTAG_HPP_INCLUDED +#define VMIME_NET_IMAP_IMAPTAG_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include "vmime/types.hpp" + + +namespace vmime { +namespace net { +namespace imap { + + +class VMIME_EXPORT IMAPTag : public object +{ +private: + + IMAPTag(const int number); + IMAPTag(const IMAPTag& tag); + +public: + + IMAPTag(); + + IMAPTag& operator++(); // ++IMAPTag + const IMAPTag operator++(int); // IMAPTag++ + + int maximumNumber() const; + int number() const; + + operator string() const; + +private: + + void generate(); + + static const int sm_maxNumber; + + int m_number; + string m_tag; +}; + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + +#endif // VMIME_NET_IMAP_IMAPTAG_HPP_INCLUDED diff --git a/src/vmime/net/imap/IMAPUtils.cpp b/src/vmime/net/imap/IMAPUtils.cpp new file mode 100644 index 00000000..ff81ce71 --- /dev/null +++ b/src/vmime/net/imap/IMAPUtils.cpp @@ -0,0 +1,758 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include "vmime/net/imap/IMAPUtils.hpp" +#include "vmime/net/imap/IMAPStore.hpp" + +#include "vmime/net/message.hpp" +#include "vmime/net/folder.hpp" + +#include <sstream> +#include <iterator> +#include <algorithm> + + +namespace vmime { +namespace net { +namespace imap { + + +// static +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() ; 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 (size_t 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]; + + // iconv() is buggy with UTF-8 to UTF-7 conversion, so we do it "by hand". + // This code is largely inspired from "imap/utf7.c", in mutt 1.4. + // Copyright (C) 2000 Edmund Grimley Evans <[email protected]> + + // WARNING: This may throw "exceptions::charset_conv_error" + const string cvt = text.getConvertedText(charset(charsets::UTF_8)); + + // In the worst case we convert 2 chars to 7 chars. + // For example: "\x10&\x10&..." -> "&ABA-&-&ABA-&-...". + string out; + out.reserve((cvt.length() / 2) * 7 + 6); + + int b = 0, k = 0; + bool base64 = false; + + size_t remaining = cvt.length(); + + for (size_t i = 0, len = cvt.length() ; i < len ; ) + { + const unsigned char c = cvt[i]; + + // Replace hierarchy separator with an equivalent UTF-7 Base64 sequence + if (!base64 && c == hierarchySeparator) + { + out += "&" + hsUTF7 + "-"; + + ++i; + --remaining; + continue; + } + + size_t n = 0; + int ch = 0; + + if (c < 0x80) + ch = c, n = 0; + else if (c < 0xc2) + return ""; + else if (c < 0xe0) + ch = c & 0x1f, n = 1; + else if (c < 0xf0) + ch = c & 0x0f, n = 2; + else if (c < 0xf8) + ch = c & 0x07, n = 3; + else if (c < 0xfc) + ch = c & 0x03, n = 4; + else if (c < 0xfe) + ch = c & 0x01, n = 5; + else + return ""; + + if (n > remaining) + return ""; // error + + ++i; + --remaining; + + for (size_t j = 0 ; j < n ; j++) + { + if ((cvt[i + j] & 0xc0) != 0x80) + return ""; // error + + ch = (ch << 6) | (cvt[i + j] & 0x3f); + } + + if (n > 1 && !(ch >> (n * 5 + 1))) + return ""; // error + + i += n; + remaining -= n; + + if (ch < 0x20 || ch >= 0x7f) + { + if (!base64) + { + out += '&'; + base64 = true; + b = 0; + k = 10; + } + + if (ch & ~0xffff) + ch = 0xfffe; + + out += base64alphabet[b | ch >> k]; + + k -= 6; + + for ( ; k >= 0 ; k -= 6) + out += base64alphabet[(ch >> k) & 0x3f]; + + b = (ch << (-k)) & 0x3f; + k += 16; + } + else + { + if (base64) + { + if (k > 10) + out += base64alphabet[b]; + + out += '-'; + base64 = false; + } + + out += static_cast <char>(ch); + + if (ch == '&') + out += '-'; + } + } + + if (base64) + { + if (k > 10) + out += base64alphabet[b]; + + out += '-'; + } + + 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))); +} + + +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); +} + + +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); +} + + +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; + case IMAPParser::flag::DRAFT: + flags |= message::FLAG_DRAFT; + break; + + default: + //case IMAPParser::flag::UNKNOWN: + 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 (flags & message::FLAG_DRAFT) flagList.push_back("\\Draft"); + + if (!flagList.empty()) + { + std::ostringstream res; + res.imbue(std::locale::classic()); + + 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 ""; +} + + +// static +const string IMAPUtils::dateTime(const vmime::datetime& date) +{ + std::ostringstream res; + res.imbue(std::locale::classic()); + + // 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()); +} + + +// static +const string IMAPUtils::buildFetchRequest + (shared_ptr <IMAPConnection> cnt, const messageSet& msgs, const fetchAttributes& options) +{ + // 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.has(fetchAttributes::SIZE)) + items.push_back("RFC822.SIZE"); + + if (options.has(fetchAttributes::FLAGS)) + items.push_back("FLAGS"); + + if (options.has(fetchAttributes::STRUCTURE)) + items.push_back("BODYSTRUCTURE"); + + if (options.has(fetchAttributes::UID)) + { + items.push_back("UID"); + + // Also fetch MODSEQ if CONDSTORE is supported + if (cnt->hasCapability("CONDSTORE") && !cnt->isMODSEQDisabled()) + items.push_back("MODSEQ"); + } + + if (options.has(fetchAttributes::FULL_HEADER)) + items.push_back("RFC822.HEADER"); + else + { + if (options.has(fetchAttributes::ENVELOPE)) + items.push_back("ENVELOPE"); + + std::vector <string> headerFields; + + if (options.has(fetchAttributes::CONTENT_INFO)) + headerFields.push_back("CONTENT_TYPE"); + + if (options.has(fetchAttributes::IMPORTANCE)) + { + headerFields.push_back("IMPORTANCE"); + headerFields.push_back("X-PRIORITY"); + } + + // Also add custom header fields to fetch, if any + const std::vector <string> customHeaderFields = options.getHeaderFields(); + std::copy(customHeaderFields.begin(), customHeaderFields.end(), std::back_inserter(headerFields)); + + 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.imbue(std::locale::classic()); + + if (msgs.isUIDSet()) + command << "UID FETCH " << messageSetToSequenceSet(msgs) << " ("; + else + command << "FETCH " << messageSetToSequenceSet(msgs) << " ("; + + for (std::vector <string>::const_iterator it = items.begin() ; + it != items.end() ; ++it) + { + if (it != items.begin()) command << " "; + command << *it; + } + + command << ")"; + + return command.str(); +} + + +// static +void IMAPUtils::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(make_shared <mailbox>(name, email)); + } +} + + + +class IMAPUIDMessageSetEnumerator : public messageSetEnumerator +{ +public: + + IMAPUIDMessageSetEnumerator() + : m_first(true) + { + } + + void enumerateNumberMessageRange(const vmime::net::numberMessageRange& range) + { + if (!m_first) + m_oss << ","; + + if (range.getFirst() == range.getLast()) + m_oss << range.getFirst(); + else + m_oss << range.getFirst() << ":" << range.getLast(); + + m_first = false; + } + + void enumerateUIDMessageRange(const vmime::net::UIDMessageRange& range) + { + if (!m_first) + m_oss << ","; + + if (range.getFirst() == range.getLast()) + m_oss << range.getFirst(); + else + m_oss << range.getFirst() << ":" << range.getLast(); + + m_first = false; + } + + const std::string str() const + { + return m_oss.str(); + } + +private: + + std::ostringstream m_oss; + bool m_first; +}; + + +class IMAPMessageSetEnumerator : public messageSetEnumerator +{ +public: + + void enumerateNumberMessageRange(const vmime::net::numberMessageRange& range) + { + for (int i = range.getFirst(), last = range.getLast() ; i <= last ; ++i) + m_list.push_back(i); + } + + void enumerateUIDMessageRange(const vmime::net::UIDMessageRange& /* range */) + { + // Not used + } + + const std::vector <int>& list() const + { + return m_list; + } + +public: + + std::vector <int> m_list; +}; + + + +// static +const string IMAPUtils::messageSetToSequenceSet(const messageSet& msgs) +{ + IMAPUIDMessageSetEnumerator en; + msgs.enumerate(en); + + return en.str(); +} + + +// static +const std::vector <int> IMAPUtils::messageSetToNumberList(const messageSet& msgs) +{ + IMAPMessageSetEnumerator en; + msgs.enumerate(en); + + return en.list(); +} + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + diff --git a/src/vmime/net/imap/IMAPUtils.hpp b/src/vmime/net/imap/IMAPUtils.hpp new file mode 100644 index 00000000..988b6a2c --- /dev/null +++ b/src/vmime/net/imap/IMAPUtils.hpp @@ -0,0 +1,129 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_IMAP_IMAPUTILS_HPP_INCLUDED +#define VMIME_NET_IMAP_IMAPUTILS_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + + +#include "vmime/types.hpp" +#include "vmime/dateTime.hpp" + +#include "vmime/net/folder.hpp" +#include "vmime/net/message.hpp" +#include "vmime/net/imap/IMAPParser.hpp" +#include "vmime/net/imap/IMAPConnection.hpp" + +#include "vmime/mailboxList.hpp" + +#include <vector> + + +namespace vmime { +namespace net { +namespace imap { + + +class VMIME_EXPORT IMAPUtils +{ +public: + + static const string pathToString(const char hierarchySeparator, const folder::path& path); + static const folder::path stringToPath(const char hierarchySeparator, const string& str); + + static const string toModifiedUTF7(const char hierarchySeparator, const folder::path::component& text); + static const folder::path::component fromModifiedUTF7(const string& text); + + /** Quote string if it contains IMAP-special characters. + * + * @param text string to quote + * @return quoted string + */ + static const string quoteString(const string& text); + + static int folderTypeFromFlags(const IMAPParser::mailbox_flag_list* list); + static int folderFlagsFromFlags(const IMAPParser::mailbox_flag_list* list); + + static int messageFlagsFromFlags(const IMAPParser::flag_list* list); + + static const string messageFlagList(const int flags); + + /** Format a date/time to IMAP date/time format. + * + * @param date date/time to format + * @return IMAP-formatted date/time + */ + static const string dateTime(const vmime::datetime& date); + + /** Construct a fetch request for the specified messages, designated + * either by their sequence numbers or their UIDs. + * + * @param cnt connection + * @param msgs message set + * @param options fetch options + * @return fetch request + */ + static const string buildFetchRequest + (shared_ptr <IMAPConnection> cnt, const messageSet& msgs, const fetchAttributes& options); + + /** Convert a parser-style address list to a mailbox list. + * + * @param src input address list + * @param dest output mailbox list + */ + static void convertAddressList(const IMAPParser::address_list& src, mailboxList& dest); + + /** Returns an IMAP-formatted sequence set given a message set. + * + * @param msgs message set + * @return IMAP sequence set (eg. "1:5,7,15:*") + */ + static const string messageSetToSequenceSet(const messageSet& msgs); + + /** Returns a list of message sequence numbers given a message set. + * + * @param msgs message set + * @return list of message numbers + */ + static const std::vector <int> messageSetToNumberList(const messageSet& msgs); + +private: + + static const string buildFetchRequestImpl + (shared_ptr <IMAPConnection> cnt, const string& mode, const string& set, const int options); +}; + + +} // imap +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP + +#endif // VMIME_NET_IMAP_IMAPUTILS_HPP_INCLUDED diff --git a/src/vmime/net/imap/imap.hpp b/src/vmime/net/imap/imap.hpp new file mode 100644 index 00000000..5e10619a --- /dev/null +++ b/src/vmime/net/imap/imap.hpp @@ -0,0 +1,35 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_IMAP_IMAP_HPP_INCLUDED +#define VMIME_NET_IMAP_IMAP_HPP_INCLUDED + + +#include "vmime/net/imap/IMAPFolder.hpp" +#include "vmime/net/imap/IMAPFolderStatus.hpp" +#include "vmime/net/imap/IMAPMessage.hpp" +#include "vmime/net/imap/IMAPStore.hpp" +#include "vmime/net/imap/IMAPSStore.hpp" + + +#endif // VMIME_NET_IMAP_IMAP_HPP_INCLUDED diff --git a/src/vmime/net/maildir/format/courierMaildirFormat.cpp b/src/vmime/net/maildir/format/courierMaildirFormat.cpp new file mode 100644 index 00000000..6d460d5e --- /dev/null +++ b/src/vmime/net/maildir/format/courierMaildirFormat.cpp @@ -0,0 +1,542 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + + +#include "vmime/net/maildir/format/courierMaildirFormat.hpp" + +#include "vmime/net/maildir/maildirStore.hpp" +#include "vmime/net/maildir/maildirUtils.hpp" + +#include "vmime/platform.hpp" + + +namespace vmime { +namespace net { +namespace maildir { +namespace format { + + +courierMaildirFormat::courierMaildirFormat(shared_ptr <context> ctx) + : maildirFormat(ctx) +{ +} + + +const string courierMaildirFormat::getName() const +{ + return "courier"; +} + + +void courierMaildirFormat::createFolder(const folder::path& path) +{ + shared_ptr <utility::fileSystemFactory> fsf = platform::getHandler()->getFileSystemFactory(); + + if (!fsf->isValidPath(folderPathToFileSystemPath(path, ROOT_DIRECTORY))) + throw exceptions::invalid_folder_name(); + + shared_ptr <utility::file> rootDir = fsf->create + (folderPathToFileSystemPath(path, ROOT_DIRECTORY)); + + shared_ptr <utility::file> newDir = fsf->create + (folderPathToFileSystemPath(path, NEW_DIRECTORY)); + shared_ptr <utility::file> tmpDir = fsf->create + (folderPathToFileSystemPath(path, TMP_DIRECTORY)); + shared_ptr <utility::file> curDir = fsf->create + (folderPathToFileSystemPath(path, CUR_DIRECTORY)); + + rootDir->createDirectory(true); + + newDir->createDirectory(false); + tmpDir->createDirectory(false); + curDir->createDirectory(false); + + shared_ptr <utility::file> maildirFile = fsf->create + (folderPathToFileSystemPath(path, ROOT_DIRECTORY) + / utility::file::path::component("maildirfolder")); + + maildirFile->createFile(); +} + + +void courierMaildirFormat::destroyFolder(const folder::path& path) +{ + shared_ptr <utility::fileSystemFactory> fsf = platform::getHandler()->getFileSystemFactory(); + + // Recursively delete directories of subfolders + const std::vector <folder::path> folders = listFolders(path, true); + + for (std::vector <folder::path>::size_type i = 0, n = folders.size() ; i < n ; ++i) + { + maildirUtils::recursiveFSDelete(fsf->create + (folderPathToFileSystemPath(folders[i], ROOT_DIRECTORY))); + } + + // Recursively delete the directory of this folder + maildirUtils::recursiveFSDelete(fsf->create + (folderPathToFileSystemPath(path, ROOT_DIRECTORY))); +} + + +void courierMaildirFormat::renameFolder + (const folder::path& oldPath, const folder::path& newPath) +{ + const std::vector <folder::path> folders = listFolders(oldPath, true); + + for (std::vector <folder::path>::size_type i = 0, n = folders.size() ; i < n ; ++i) + { + const folder::path folderOldPath = folders[i]; + + folder::path folderNewPath = folderOldPath; + folderNewPath.renameParent(oldPath, newPath); + + renameFolderImpl(folderOldPath, folderNewPath); + } + + renameFolderImpl(oldPath, newPath); +} + + +void courierMaildirFormat::renameFolderImpl + (const folder::path& oldPath, const folder::path& newPath) +{ + shared_ptr <utility::fileSystemFactory> fsf = platform::getHandler()->getFileSystemFactory(); + + const utility::file::path oldFSPath = + folderPathToFileSystemPath(oldPath, ROOT_DIRECTORY); + + const utility::file::path newFSPath = + folderPathToFileSystemPath(newPath, ROOT_DIRECTORY); + + shared_ptr <utility::file> rootDir = fsf->create(oldFSPath); + rootDir->rename(newFSPath); +} + + +bool courierMaildirFormat::folderExists(const folder::path& path) const +{ + shared_ptr <utility::fileSystemFactory> fsf = platform::getHandler()->getFileSystemFactory(); + + shared_ptr <utility::file> rootDir = fsf->create + (folderPathToFileSystemPath(path, ROOT_DIRECTORY)); + + shared_ptr <utility::file> newDir = fsf->create + (folderPathToFileSystemPath(path, NEW_DIRECTORY)); + shared_ptr <utility::file> tmpDir = fsf->create + (folderPathToFileSystemPath(path, TMP_DIRECTORY)); + shared_ptr <utility::file> curDir = fsf->create + (folderPathToFileSystemPath(path, CUR_DIRECTORY)); + + shared_ptr <utility::file> maildirFile = fsf->create + (folderPathToFileSystemPath(path, ROOT_DIRECTORY) + / utility::file::path::component("maildirfolder")); + + bool exists = rootDir->exists() && rootDir->isDirectory() && + newDir->exists() && newDir->isDirectory() && + tmpDir->exists() && tmpDir->isDirectory() && + curDir->exists() && curDir->isDirectory(); + + // If this is not the root folder, then a file named "maildirfolder" + // must also be present in the directory + if (!path.isRoot()) + exists = exists && maildirFile->exists() && maildirFile->isFile(); + + return exists; +} + + +bool courierMaildirFormat::folderHasSubfolders(const folder::path& path) const +{ + std::vector <string> dirs; + return listDirectories(path, dirs, true); +} + + +const utility::file::path courierMaildirFormat::folderPathToFileSystemPath + (const folder::path& path, const DirectoryType type) const +{ + // Virtual folder "/MyFolder/SubFolder" corresponds to physical + // directory "[store root]/.MyFolder.SubFolder" + utility::file::path fsPath = getContext()->getStore()->getFileSystemPath(); + + if (!path.isRoot()) + { + string folderComp; + + for (size_t i = 0, n = path.getSize() ; i < n ; ++i) + folderComp += "." + toModifiedUTF7(path[i]); + + fsPath /= utility::file::path::component(folderComp); + } + + // Last component + switch (type) + { + case ROOT_DIRECTORY: + + // Nothing to add + break; + + case NEW_DIRECTORY: + + fsPath /= NEW_DIR; + break; + + case CUR_DIRECTORY: + + fsPath /= CUR_DIR; + break; + + case TMP_DIRECTORY: + + fsPath /= TMP_DIR; + break; + + case CONTAINER_DIRECTORY: + + // Not used + break; + } + + return fsPath; +} + + +const std::vector <folder::path> courierMaildirFormat::listFolders + (const folder::path& root, const bool recursive) const +{ + // First, list directories + std::vector <string> dirs; + listDirectories(root, dirs, false); + + // Then, map directories to folders + std::vector <folder::path> folders; + + for (std::vector <string>::size_type i = 0, n = dirs.size() ; i < n ; ++i) + { + const string dir = dirs[i].substr(1) + "."; + folder::path path; + + for (size_t pos = dir.find("."), prev = 0 ; + pos != string::npos ; prev = pos + 1, pos = dir.find(".", pos + 1)) + { + const string comp = dir.substr(prev, pos - prev); + path /= fromModifiedUTF7(comp); + } + + if (recursive || path.getSize() == root.getSize() + 1) + folders.push_back(path); + } + + return folders; +} + + +bool courierMaildirFormat::listDirectories(const folder::path& root, + std::vector <string>& dirs, const bool onlyTestForExistence) const +{ + shared_ptr <utility::fileSystemFactory> fsf = platform::getHandler()->getFileSystemFactory(); + + shared_ptr <utility::file> rootDir = fsf->create + (getContext()->getStore()->getFileSystemPath()); + + if (rootDir->exists()) + { + // To speed up things, and if we are not searching in root folder, + // search for directories with a common prefix + string base; + + if (!root.isRoot()) + { + for (size_t i = 0, n = root.getSize() ; i < n ; ++i) + base += "." + toModifiedUTF7(root[i]); + } + + // Enumerate directories + shared_ptr <utility::fileIterator> it = rootDir->getFiles(); + + while (it->hasMoreElements()) + { + shared_ptr <utility::file> file = it->nextElement(); + + if (isSubfolderDirectory(*file)) + { + const string dir = file->getFullPath().getLastComponent().getBuffer(); + + if (base.empty() || (dir.length() > base.length() && dir.substr(0, base.length()) == base)) + { + dirs.push_back(dir); + + if (onlyTestForExistence) + return true; + } + } + } + } + else + { + // No sub-folder + } + + std::sort(dirs.begin(), dirs.end()); + + return !dirs.empty(); +} + + +// static +bool courierMaildirFormat::isSubfolderDirectory(const utility::file& file) +{ + // A directory which names starts with '.' may be a subfolder + if (file.isDirectory() && + file.getFullPath().getLastComponent().getBuffer().length() >= 1 && + file.getFullPath().getLastComponent().getBuffer()[0] == '.') + { + return true; + } + + return false; +} + + +// static +const string courierMaildirFormat::toModifiedUTF7(const folder::path::component& text) +{ + // From http://www.courier-mta.org/?maildir.html: + // + // Folder names can contain any Unicode character, except for control + // characters. US-ASCII characters, U+0x0020 - U+0x007F, except for the + // period, forward-slash, and ampersand characters (U+0x002E, U+0x002F, + // and U+0x0026) represent themselves. The ampersand is represented by + // the two character sequence "&-". The period, forward slash, and non + // US-ASCII Unicode characters are represented using the UTF-7 character + // set, and encoded with a modified form of base64-encoding. + // + // The "&" character starts the modified base64-encoded sequence; the + // sequence is terminated by the "-" character. The sequence of 16-bit + // Unicode characters is written in big-endian order, and encoded using + // the base64-encoding method described in section 5.2 of RFC 1521, with + // the following modifications: + // + // * The "=" padding character is omitted. When decoding, an incomplete + // 16-bit character is discarded. + // + // * The comma character, "," is used in place of the "/" character in + // the base64 alphabet. + // + // For example, the word "Resume" with both "e"s being the e-acute + // character, U+0x00e9, is encoded as "R&AOk-sum&AOk-" (so a folder of + // that name would be a maildir subdirectory called ".R&AOk-sum&AOk-"). + // + + // 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; + + 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, + // and simply UTF7-encoded out of a Base64 sequence + case '/': + { + if (inB64sequence) + out += ','; + else + out += "&Lw-"; + + break; + } + // Encode period (should not happen in a Base64 sequence) + case '.': + { + out += "&Lg-"; + break; + } + // '&' (0x26) is represented by the two-octet sequence "&-" + case '&': + { + if (!inB64sequence) + out += "&-"; + else + out += '&'; + + break; + } + default: + { + out += c; + break; + } + + } + } + + return out; +} + + +// static +const folder::path::component courierMaildirFormat::fromModifiedUTF7(const string& text) +{ + // Transcode from modified UTF-7 + 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))); +} + + +bool courierMaildirFormat::supports() const +{ + shared_ptr <utility::fileSystemFactory> fsf = platform::getHandler()->getFileSystemFactory(); + + shared_ptr <utility::file> rootDir = fsf->create + (getContext()->getStore()->getFileSystemPath()); + + if (rootDir->exists()) + { + // Try to find a file named "maildirfolder", which indicates + // the Maildir is in Courier format + shared_ptr <utility::fileIterator> it = rootDir->getFiles(); + + while (it->hasMoreElements()) + { + shared_ptr <utility::file> file = it->nextElement(); + + if (isSubfolderDirectory(*file)) + { + shared_ptr <utility::file> folderFile = fsf->create + (file->getFullPath() / utility::file::path::component("maildirfolder")); + + if (folderFile->exists() && folderFile->isFile()) + return true; + } + } + } + + return false; +} + + +} // format +} // maildir +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + diff --git a/src/vmime/net/maildir/format/courierMaildirFormat.hpp b/src/vmime/net/maildir/format/courierMaildirFormat.hpp new file mode 100644 index 00000000..b8443426 --- /dev/null +++ b/src/vmime/net/maildir/format/courierMaildirFormat.hpp @@ -0,0 +1,121 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_MAILDIR_FORMAT_COURIERMAILDIRFORMAT_HPP_INCLUDED +#define VMIME_NET_MAILDIR_FORMAT_COURIERMAILDIRFORMAT_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + + +#include "vmime/net/maildir/maildirFormat.hpp" + + +namespace vmime { +namespace net { +namespace maildir { +namespace format { + + +/** Reads Courier/QMail Maildir format. + */ + +class VMIME_EXPORT courierMaildirFormat : public maildirFormat +{ +public: + + courierMaildirFormat(shared_ptr <context> ctx); + + + /* Folder types: + * + * - ROOT_DIRECTORY: ~/Mail/.MyFolder + * - NEW_DIRECTORY: ~/Mail/.MyFolder/new + * - CUR_DIRECTORY: ~/Mail/.MyFolder/cur + * - TMP_DIRECTORY: ~/Mail/.MyFolder/tmp + * - CONTAINER_DIRECTORY: not used + */ + + const string getName() const; + + void createFolder(const folder::path& path); + void destroyFolder(const folder::path& path); + void renameFolder(const folder::path& oldPath, const folder::path& newPath); + + bool folderExists(const folder::path& path) const; + bool folderHasSubfolders(const folder::path& path) const; + + const utility::file::path folderPathToFileSystemPath + (const folder::path& path, const DirectoryType type) const; + + const std::vector <folder::path> listFolders + (const folder::path& root, const bool recursive) const; + +protected: + + bool supports() const; + + + static const string toModifiedUTF7(const folder::path::component& text); + static const folder::path::component fromModifiedUTF7(const string& text); + + void renameFolderImpl(const folder::path& oldPath, const folder::path& newPath); + + /** Test whether the specified file system directory corresponds to + * a maildir subfolder. The name of the directory should start + * with a '.' to be listed as a subfolder. + * + * @param file reference to a file system directory + * @return true if the specified directory is a maildir subfolder, + * false otherwise + */ + static bool isSubfolderDirectory(const utility::file& file); + + /** List directories corresponding to folders which are (direct or + * indirect) children of specified folder. + * + * @param root root folder + * @param dirs list in which found directories will be added + * @param onlyTestForExistence if true, the function returns as soon + * as the first directory is found + * @return true if at least one directory has been found, + * false otherwise + */ + bool listDirectories(const folder::path& root, + std::vector <string>& dirs, const bool onlyTestForExistence) const; +}; + + +} // format +} // maildir +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + +#endif // VMIME_NET_MAILDIR_FORMAT_COURIERMAILDIRFORMAT_HPP_INCLUDED + diff --git a/src/vmime/net/maildir/format/kmailMaildirFormat.cpp b/src/vmime/net/maildir/format/kmailMaildirFormat.cpp new file mode 100644 index 00000000..975752a5 --- /dev/null +++ b/src/vmime/net/maildir/format/kmailMaildirFormat.cpp @@ -0,0 +1,320 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + + +#include "vmime/net/maildir/format/kmailMaildirFormat.hpp" + +#include "vmime/net/maildir/maildirStore.hpp" +#include "vmime/net/maildir/maildirUtils.hpp" + +#include "vmime/platform.hpp" + + +namespace vmime { +namespace net { +namespace maildir { +namespace format { + + +kmailMaildirFormat::kmailMaildirFormat(shared_ptr <context> ctx) + : maildirFormat(ctx) +{ +} + + +const string kmailMaildirFormat::getName() const +{ + return "kmail"; +} + + +void kmailMaildirFormat::createFolder(const folder::path& path) +{ + shared_ptr <utility::fileSystemFactory> fsf = platform::getHandler()->getFileSystemFactory(); + + if (!fsf->isValidPath(folderPathToFileSystemPath(path, ROOT_DIRECTORY))) + throw exceptions::invalid_folder_name(); + + shared_ptr <utility::file> rootDir = fsf->create + (folderPathToFileSystemPath(path, ROOT_DIRECTORY)); + + shared_ptr <utility::file> newDir = fsf->create + (folderPathToFileSystemPath(path, NEW_DIRECTORY)); + shared_ptr <utility::file> tmpDir = fsf->create + (folderPathToFileSystemPath(path, TMP_DIRECTORY)); + shared_ptr <utility::file> curDir = fsf->create + (folderPathToFileSystemPath(path, CUR_DIRECTORY)); + + rootDir->createDirectory(true); + + newDir->createDirectory(false); + tmpDir->createDirectory(false); + curDir->createDirectory(false); +} + + +void kmailMaildirFormat::destroyFolder(const folder::path& path) +{ + // Delete 'folder' and '.folder.directory' directories + shared_ptr <utility::fileSystemFactory> fsf = platform::getHandler()->getFileSystemFactory(); + + maildirUtils::recursiveFSDelete(fsf->create + (folderPathToFileSystemPath(path, ROOT_DIRECTORY))); // root + + maildirUtils::recursiveFSDelete(fsf->create + (folderPathToFileSystemPath(path, CONTAINER_DIRECTORY))); // container +} + + +bool kmailMaildirFormat::folderExists(const folder::path& path) const +{ + shared_ptr <utility::fileSystemFactory> fsf = platform::getHandler()->getFileSystemFactory(); + + shared_ptr <utility::file> rootDir = fsf->create + (folderPathToFileSystemPath(path, ROOT_DIRECTORY)); + + shared_ptr <utility::file> newDir = fsf->create + (folderPathToFileSystemPath(path, NEW_DIRECTORY)); + shared_ptr <utility::file> tmpDir = fsf->create + (folderPathToFileSystemPath(path, TMP_DIRECTORY)); + shared_ptr <utility::file> curDir = fsf->create + (folderPathToFileSystemPath(path, CUR_DIRECTORY)); + + return rootDir->exists() && rootDir->isDirectory() && + newDir->exists() && newDir->isDirectory() && + tmpDir->exists() && tmpDir->isDirectory() && + curDir->exists() && curDir->isDirectory(); +} + + +const utility::file::path kmailMaildirFormat::folderPathToFileSystemPath + (const folder::path& path, const DirectoryType type) const +{ + // Root path + utility::file::path fsPath = getContext()->getStore()->getFileSystemPath(); + + const size_t pathSize = path.getSize(); + const size_t count = (type == CONTAINER_DIRECTORY + ? pathSize : (pathSize >= 1 ? pathSize - 1 : 0)); + + // Parent folders + for (size_t i = 0 ; i < count ; ++i) + { + utility::file::path::component comp(path[i]); + + // TODO: may not work with all encodings... + comp.setBuffer("." + comp.getBuffer() + ".directory"); + + fsPath /= comp; + } + + // Last component + if (path.getSize() != 0 && type != CONTAINER_DIRECTORY) + { + fsPath /= path.getLastComponent(); + + switch (type) + { + case ROOT_DIRECTORY: + + // Nothing to add + break; + + case NEW_DIRECTORY: + + fsPath /= NEW_DIR; + break; + + case CUR_DIRECTORY: + + fsPath /= CUR_DIR; + break; + + case TMP_DIRECTORY: + + fsPath /= TMP_DIR; + break; + + case CONTAINER_DIRECTORY: + + // Can't happen... + break; + } + } + + return fsPath; +} + + +const std::vector <folder::path> kmailMaildirFormat::listFolders + (const folder::path& root, const bool recursive) const +{ + std::vector <folder::path> list; + listFoldersImpl(list, root, recursive); + + return list; +} + + +void kmailMaildirFormat::listFoldersImpl + (std::vector <folder::path>& list, const folder::path& root, const bool recursive) const +{ + shared_ptr <utility::fileSystemFactory> fsf = platform::getHandler()->getFileSystemFactory(); + + shared_ptr <utility::file> rootDir = fsf->create(folderPathToFileSystemPath(root, + root.isEmpty() ? ROOT_DIRECTORY : CONTAINER_DIRECTORY)); + + if (rootDir->exists()) + { + shared_ptr <utility::fileIterator> it = rootDir->getFiles(); + + while (it->hasMoreElements()) + { + shared_ptr <utility::file> file = it->nextElement(); + + if (isSubfolderDirectory(*file)) + { + const utility::path subPath = + root / file->getFullPath().getLastComponent(); + + list.push_back(subPath); + + if (recursive) + listFoldersImpl(list, subPath, true); + } + } + } + else + { + // No sub-folder + } +} + + +// static +bool kmailMaildirFormat::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; +} + + +void kmailMaildirFormat::renameFolder(const folder::path& oldPath, const folder::path& newPath) +{ + shared_ptr <utility::fileSystemFactory> fsf = platform::getHandler()->getFileSystemFactory(); + + shared_ptr <utility::file> rootDir = fsf->create + (folderPathToFileSystemPath(oldPath, ROOT_DIRECTORY)); + shared_ptr <utility::file> contDir = fsf->create + (folderPathToFileSystemPath(oldPath, CONTAINER_DIRECTORY)); + + try + { + const utility::file::path newRootPath = + folderPathToFileSystemPath(newPath, ROOT_DIRECTORY); + const utility::file::path newContPath = + folderPathToFileSystemPath(newPath, CONTAINER_DIRECTORY); + + 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 = + folderPathToFileSystemPath(oldPath, ROOT_DIRECTORY); + const utility::file::path contPath = + folderPathToFileSystemPath(oldPath, CONTAINER_DIRECTORY); + + try + { + rootDir->rename(rootPath); + contDir->rename(contPath); + } + catch (exceptions::filesystem_exception& e) + { + // Ignore (not recoverable) + } + + throw; + } +} + + +bool kmailMaildirFormat::folderHasSubfolders(const folder::path& path) const +{ + shared_ptr <utility::fileSystemFactory> fsf = platform::getHandler()->getFileSystemFactory(); + + shared_ptr <utility::file> rootDir = fsf->create + (folderPathToFileSystemPath(path, CONTAINER_DIRECTORY)); + + shared_ptr <utility::fileIterator> it = rootDir->getFiles(); + + while (it->hasMoreElements()) + { + shared_ptr <utility::file> file = it->nextElement(); + + if (isSubfolderDirectory(*file)) + return true; + } + + return false; +} + + +bool kmailMaildirFormat::supports() const +{ + // This is the default + return true; +} + + +} // format +} // maildir +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + diff --git a/src/vmime/net/maildir/format/kmailMaildirFormat.hpp b/src/vmime/net/maildir/format/kmailMaildirFormat.hpp new file mode 100644 index 00000000..98ca212e --- /dev/null +++ b/src/vmime/net/maildir/format/kmailMaildirFormat.hpp @@ -0,0 +1,109 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_MAILDIR_FORMAT_KMAILMAILDIRFORMAT_HPP_INCLUDED +#define VMIME_NET_MAILDIR_FORMAT_KMAILMAILDIRFORMAT_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + + +#include "vmime/net/maildir/maildirFormat.hpp" + + +namespace vmime { +namespace net { +namespace maildir { +namespace format { + + +/** Reads KMail Maildir format. + */ + +class VMIME_EXPORT kmailMaildirFormat : public maildirFormat +{ +public: + + kmailMaildirFormat(shared_ptr <context> ctx); + + + /* Folder types: + * + * - ROOT_DIRECTORY: ~/Mail/MyFolder + * - NEW_DIRECTORY: ~/Mail/MyFolder/new + * - CUR_DIRECTORY: ~/Mail/MyFolder/cur + * - TMP_DIRECTORY: ~/Mail/MyFolder/tmp + * - CONTAINER_DIRECTORY: ~/Mail/.MyFolder.directory + */ + + const string getName() const; + + void createFolder(const folder::path& path); + void destroyFolder(const folder::path& path); + void renameFolder(const folder::path& oldPath, const folder::path& newPath); + + bool folderExists(const folder::path& path) const; + bool folderHasSubfolders(const folder::path& path) const; + + const utility::file::path folderPathToFileSystemPath + (const folder::path& path, const DirectoryType type) const; + + const std::vector <folder::path> listFolders + (const folder::path& root, const bool recursive) const; + +protected: + + bool supports() const; + + + /** Recursive implementation of listFolders(). + */ + void listFoldersImpl(std::vector <folder::path>& list, + const folder::path& root, const bool recursive) const; + + /** Test whether the specified file system directory corresponds to + * a maildir subfolder. The name of the directory should not start + * with '.' to be listed as a subfolder. + * + * @param file reference to a file system directory + * @return true if the specified directory is a maildir subfolder, + * false otherwise + */ + static bool isSubfolderDirectory(const utility::file& file); +}; + + +} // format +} // maildir +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + + +#endif // VMIME_NET_MAILDIR_FORMAT_KMAILMAILDIRFORMAT_HPP_INCLUDED + diff --git a/src/vmime/net/maildir/maildir.hpp b/src/vmime/net/maildir/maildir.hpp new file mode 100644 index 00000000..42bbbea4 --- /dev/null +++ b/src/vmime/net/maildir/maildir.hpp @@ -0,0 +1,34 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_MAILDIR_MAILDIR_HPP_INCLUDED +#define VMIME_NET_MAILDIR_MAILDIR_HPP_INCLUDED + + +#include "vmime/net/maildir/maildirFolder.hpp" +#include "vmime/net/maildir/maildirFolderStatus.hpp" +#include "vmime/net/maildir/maildirMessage.hpp" +#include "vmime/net/maildir/maildirStore.hpp" + + +#endif // VMIME_NET_MAILDIR_MAILDIR_HPP_INCLUDED diff --git a/src/vmime/net/maildir/maildirFolder.cpp b/src/vmime/net/maildir/maildirFolder.cpp new file mode 100644 index 00000000..660178ff --- /dev/null +++ b/src/vmime/net/maildir/maildirFolder.cpp @@ -0,0 +1,1254 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + + +#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/net/maildir/maildirFormat.hpp" +#include "vmime/net/maildir/maildirFolderStatus.hpp" + +#include "vmime/message.hpp" + +#include "vmime/exception.hpp" +#include "vmime/platform.hpp" + +#include "vmime/utility/outputStreamAdapter.hpp" +#include "vmime/utility/inputStreamStringAdapter.hpp" + + +namespace vmime { +namespace net { +namespace maildir { + + +maildirFolder::maildirFolder(const folder::path& path, shared_ptr <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) +{ + store->registerFolder(this); +} + + +maildirFolder::~maildirFolder() +{ + shared_ptr <maildirStore> store = m_store.lock(); + + if (store) + { + if (m_open) + close(false); + + store->unregisterFolder(this); + } + else if (m_open) + { + close(false); + } +} + + +void maildirFolder::onStoreDisconnected() +{ + m_store.reset(); +} + + +int maildirFolder::getMode() const +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + return (m_mode); +} + + +int maildirFolder::getType() +{ + if (m_path.isEmpty()) + return (TYPE_CONTAINS_FOLDERS); + else + return (TYPE_CONTAINS_FOLDERS | TYPE_CONTAINS_MESSAGES); +} + + +int maildirFolder::getFlags() +{ + int flags = 0; + + if (m_store.lock()->getFormat()->folderHasSubfolders(m_path)) + flags |= FLAG_CHILDREN; // Contains at least one sub-folder + + 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 */) +{ + shared_ptr <maildirStore> store = m_store.lock(); + + if (!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) +{ + shared_ptr <maildirStore> store = m_store.lock(); + + if (!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 */) +{ + shared_ptr <maildirStore> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + else if (isOpen()) + throw exceptions::illegal_state("Folder is open"); + else if (exists()) + throw exceptions::illegal_state("Folder already exists"); + else if (!store->isValidFolderName(m_name)) + throw exceptions::invalid_folder_name(); + + // Create directory on file system + try + { + store->getFormat()->createFolder(m_path); + } + catch (exceptions::filesystem_exception& e) + { + throw exceptions::command_error("CREATE", "", "File system exception", e); + } + + // Notify folder created + shared_ptr <events::folderEvent> event = + make_shared <events::folderEvent> + (dynamicCast <folder>(shared_from_this()), + events::folderEvent::TYPE_CREATED, m_path, m_path); + + notifyFolder(event); +} + + +void maildirFolder::destroy() +{ + shared_ptr <maildirStore> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + else if (isOpen()) + throw exceptions::illegal_state("Folder is open"); + + // Delete folder + try + { + store->getFormat()->destroyFolder(m_path); + } + catch (std::exception&) + { + // Ignore exception: anyway, we can't recover from this... + } + + // Notify folder deleted + shared_ptr <events::folderEvent> event = + make_shared <events::folderEvent> + (dynamicCast <folder>(shared_from_this()), + events::folderEvent::TYPE_DELETED, m_path, m_path); + + notifyFolder(event); +} + + +bool maildirFolder::exists() +{ + shared_ptr <maildirStore> store = m_store.lock(); + + return store->getFormat()->folderExists(m_path); +} + + +bool maildirFolder::isOpen() const +{ + return (m_open); +} + + +void maildirFolder::scanFolder() +{ + shared_ptr <maildirStore> store = m_store.lock(); + + try + { + m_messageCount = 0; + m_unreadMessageCount = 0; + + shared_ptr <utility::fileSystemFactory> fsf = platform::getHandler()->getFileSystemFactory(); + + utility::file::path newDirPath = store->getFormat()->folderPathToFileSystemPath + (m_path, maildirFormat::NEW_DIRECTORY); + shared_ptr <utility::file> newDir = fsf->create(newDirPath); + + utility::file::path curDirPath = store->getFormat()->folderPathToFileSystemPath + (m_path, maildirFormat::CUR_DIRECTORY); + shared_ptr <utility::file> curDir = fsf->create(curDirPath); + + // New received messages (new/) + shared_ptr <utility::fileIterator> nit = newDir->getFiles(); + std::vector <utility::file::path::component> newMessageFilenames; + + while (nit->hasMoreElements()) + { + shared_ptr <utility::file> file = nit->nextElement(); + + if (maildirUtils::isMessageFile(*file)) + newMessageFilenames.push_back(file->getFullPath().getLastComponent()); + } + + // Current messages (cur/) + shared_ptr <utility::fileIterator> cit = curDir->getFiles(); + std::vector <utility::file::path::component> curMessageFilenames; + + while (cit->hasMoreElements()) + { + shared_ptr <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' + shared_ptr <utility::file> file = fsf->create(newDirPath / *it); + file->rename(curDirPath / newFilename); + + // Append to message list + messageInfos msgInfos; + msgInfos.path = newFilename; + + if (maildirUtils::extractFlags(msgInfos.path) & message::FLAG_DELETED) + msgInfos.type = messageInfos::TYPE_DELETED; + else + 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; + + if (maildirUtils::extractFlags(msgInfos.path) & message::FLAG_DELETED) + msgInfos.type = messageInfos::TYPE_DELETED; + else + 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... + } +} + + +shared_ptr <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 make_shared <maildirMessage> + (dynamicCast <maildirFolder>(shared_from_this()), num); +} + + +std::vector <shared_ptr <message> > maildirFolder::getMessages(const messageSet& msgs) +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + if (msgs.isNumberSet()) + { + const std::vector <int> numbers = maildirUtils::messageSetToNumberList(msgs); + + std::vector <shared_ptr <message> > messages; + shared_ptr <maildirFolder> thisFolder = dynamicCast <maildirFolder>(shared_from_this()); + + for (std::vector <int>::const_iterator it = numbers.begin() ; it != numbers.end() ; ++it) + { + if (*it < 1|| *it > m_messageCount) + throw exceptions::message_not_found(); + + messages.push_back(make_shared <maildirMessage>(thisFolder, *it)); + } + + return messages; + } + else + { + throw exceptions::operation_not_supported(); + } +} + + +int maildirFolder::getMessageCount() +{ + return (m_messageCount); +} + + +shared_ptr <folder> maildirFolder::getFolder(const folder::path::component& name) +{ + shared_ptr <maildirStore> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + + return make_shared <maildirFolder>(m_path / name, store); +} + + +std::vector <shared_ptr <folder> > maildirFolder::getFolders(const bool recursive) +{ + shared_ptr <maildirStore> store = m_store.lock(); + + if (!isOpen() && !store) + throw exceptions::illegal_state("Store disconnected"); + + std::vector <shared_ptr <folder> > list; + + listFolders(list, recursive); + + return (list); +} + + +void maildirFolder::listFolders(std::vector <shared_ptr <folder> >& list, const bool recursive) +{ + shared_ptr <maildirStore> store = m_store.lock(); + + try + { + std::vector <folder::path> pathList = + store->getFormat()->listFolders(m_path, recursive); + + list.reserve(pathList.size()); + + for (std::vector <folder::path>::size_type i = 0, n = pathList.size() ; i < n ; ++i) + { + shared_ptr <maildirFolder> subFolder = + make_shared <maildirFolder>(pathList[i], store); + + list.push_back(subFolder); + } + } + catch (exceptions::filesystem_exception& e) + { + throw exceptions::command_error("LIST", "", "", e); + } +} + + +void maildirFolder::rename(const folder::path& newPath) +{ + shared_ptr <maildirStore> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + else if (m_path.isEmpty() || newPath.isEmpty()) + throw exceptions::illegal_operation("Cannot rename root folder"); + else if (!store->isValidFolderName(newPath.getLastComponent())) + throw exceptions::invalid_folder_name(); + + // Rename the directory on the file system + try + { + store->getFormat()->renameFolder(m_path, newPath); + } + catch (vmime::exception& e) + { + throw exceptions::command_error("RENAME", "", "", e); + } + + // Notify folder renamed + folder::path oldPath(m_path); + + m_path = newPath; + m_name = newPath.getLastComponent(); + + shared_ptr <events::folderEvent> event = + make_shared <events::folderEvent> + (dynamicCast <folder>(shared_from_this()), + events::folderEvent::TYPE_RENAMED, oldPath, newPath); + + notifyFolder(event); + + // Notify folders with the same path + for (std::list <maildirFolder*>::iterator it = store->m_folders.begin() ; + it != store->m_folders.end() ; ++it) + { + if ((*it) != this && (*it)->getFullPath() == oldPath) + { + (*it)->m_path = newPath; + (*it)->m_name = newPath.getLastComponent(); + + shared_ptr <events::folderEvent> event = + make_shared <events::folderEvent> + (dynamicCast <folder>((*it)->shared_from_this()), + 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); + + shared_ptr <events::folderEvent> event = + make_shared <events::folderEvent> + (dynamicCast <folder>((*it)->shared_from_this()), + events::folderEvent::TYPE_RENAMED, oldPath, (*it)->m_path); + + (*it)->notifyFolder(event); + } + } +} + + +void maildirFolder::deleteMessages(const messageSet& msgs) +{ + // Mark messages as deleted + setMessageFlags(msgs, message::FLAG_DELETED, message::FLAG_MODE_ADD); +} + + +void maildirFolder::setMessageFlags + (const messageSet& msgs, const int flags, const int mode) +{ + shared_ptr <maildirStore> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + else if (m_mode == MODE_READ_ONLY) + throw exceptions::illegal_state("Folder is read-only"); + + if (msgs.isNumberSet()) + { + const std::vector <int> nums = maildirUtils::messageSetToNumberList(msgs); + + // Change message flags + shared_ptr <utility::fileSystemFactory> fsf = platform::getHandler()->getFileSystemFactory(); + + utility::file::path curDirPath = store->getFormat()-> + folderPathToFileSystemPath(m_path, maildirFormat::CUR_DIRECTORY); + + 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; + shared_ptr <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) + } + } + + // 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(nums.begin(), nums.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(nums.begin(), nums.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(nums.begin(), nums.end(), (*it)->getNumber()) && + (*it)->m_flags != message::FLAG_UNDEFINED) + { + (*it)->m_flags = flags; + } + } + + break; + } + + } + + // Notify message flags changed + shared_ptr <events::messageChangedEvent> event = + make_shared <events::messageChangedEvent> + (dynamicCast <folder>(shared_from_this()), + events::messageChangedEvent::TYPE_FLAGS, nums); + + notifyMessageChanged(event); + + // TODO: notify other folders with the same path + } + else + { + throw exceptions::operation_not_supported(); + } +} + + +void maildirFolder::addMessage(shared_ptr <vmime::message> msg, const int flags, + vmime::datetime* date, utility::progressListener* progress) +{ + std::ostringstream oss; + utility::outputStreamAdapter ossAdapter(oss); + + msg->generate(ossAdapter); + + const string& str = oss.str(); + utility::inputStreamStringAdapter strAdapter(str); + + addMessage(strAdapter, str.length(), flags, date, progress); +} + + +void maildirFolder::addMessage(utility::inputStream& is, const size_t size, + const int flags, vmime::datetime* /* date */, utility::progressListener* progress) +{ + shared_ptr <maildirStore> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + else if (m_mode == MODE_READ_ONLY) + throw exceptions::illegal_state("Folder is read-only"); + + shared_ptr <utility::fileSystemFactory> fsf = platform::getHandler()->getFileSystemFactory(); + + utility::file::path tmpDirPath = store->getFormat()-> + folderPathToFileSystemPath(m_path,maildirFormat::TMP_DIRECTORY); + utility::file::path dstDirPath = store->getFormat()-> + folderPathToFileSystemPath(m_path, + flags == message::FLAG_RECENT ? + maildirFormat::NEW_DIRECTORY : + maildirFormat::CUR_DIRECTORY); + + const utility::file::path::component filename = + maildirUtils::buildFilename(maildirUtils::generateId(), + ((flags == message::FLAG_UNDEFINED) ? 0 : flags)); + + try + { + shared_ptr <utility::file> tmpDir = fsf->create(tmpDirPath); + tmpDir->createDirectory(true); + } + catch (exceptions::filesystem_exception&) + { + // Don't throw now, it will fail later... + } + + try + { + shared_ptr <utility::file> curDir = fsf->create(dstDirPath); + curDir->createDirectory(true); + } + catch (exceptions::filesystem_exception&) + { + // Don't throw now, it will fail later... + } + + // Actually add the message + copyMessageImpl(tmpDirPath, dstDirPath, 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); + + shared_ptr <events::messageCountEvent> event = + make_shared <events::messageCountEvent> + (dynamicCast <folder>(shared_from_this()), + events::messageCountEvent::TYPE_ADDED, nums); + + notifyMessageCount(event); + + // Notify folders with the same path + for (std::list <maildirFolder*>::iterator it = store->m_folders.begin() ; + it != 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()); + + shared_ptr <events::messageCountEvent> event = + make_shared <events::messageCountEvent> + (dynamicCast <folder>((*it)->shared_from_this()), + events::messageCountEvent::TYPE_ADDED, nums); + + (*it)->notifyMessageCount(event); + } + } +} + + +void maildirFolder::copyMessageImpl(const utility::file::path& tmpDirPath, + const utility::file::path& dstDirPath, + const utility::file::path::component& filename, + utility::inputStream& is, const size_t size, + utility::progressListener* progress) +{ + shared_ptr <utility::fileSystemFactory> fsf = platform::getHandler()->getFileSystemFactory(); + + shared_ptr <utility::file> file = fsf->create(tmpDirPath / filename); + + if (progress) + progress->start(size); + + // First, write the message into 'tmp'... + try + { + file->createFile(); + + shared_ptr <utility::fileWriter> fw = file->getFileWriter(); + shared_ptr <utility::outputStream> os = fw->getOutputStream(); + + byte_t buffer[65536]; + size_t total = 0; + + while (!is.eof()) + { + const size_t read = is.read(buffer, sizeof(buffer)); + + if (read != 0) + { + os->write(buffer, read); + total += read; + } + + if (progress) + progress->progress(total, size); + } + + os->flush(); + } + catch (exception& e) + { + if (progress) + progress->stop(size); + + // Delete temporary file + try + { + shared_ptr <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(dstDirPath / filename); + } + catch (exception& e) + { + if (progress) + progress->stop(size); + + // Delete temporary file + try + { + file->remove(); + shared_ptr <utility::file> file = fsf->create(dstDirPath / filename); + file->remove(); + } + catch (exceptions::filesystem_exception&) + { + // Ignore + } + + throw exceptions::command_error("ADD", "", "", e); + } + + if (progress) + progress->stop(size); +} + + +void maildirFolder::copyMessages(const folder::path& dest, const messageSet& msgs) +{ + shared_ptr <maildirStore> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + shared_ptr <utility::fileSystemFactory> fsf = platform::getHandler()->getFileSystemFactory(); + + utility::file::path curDirPath = store->getFormat()->folderPathToFileSystemPath + (m_path, maildirFormat::CUR_DIRECTORY); + + utility::file::path destCurDirPath = store->getFormat()-> + folderPathToFileSystemPath(dest, maildirFormat::CUR_DIRECTORY); + utility::file::path destTmpDirPath = store->getFormat()-> + folderPathToFileSystemPath(dest, maildirFormat::TMP_DIRECTORY); + + // Create destination directories + try + { + shared_ptr <utility::file> destTmpDir = fsf->create(destTmpDirPath); + destTmpDir->createDirectory(true); + } + catch (exceptions::filesystem_exception&) + { + // Don't throw now, it will fail later... + } + + try + { + shared_ptr <utility::file> destCurDir = fsf->create(destCurDirPath); + destCurDir->createDirectory(true); + } + catch (exceptions::filesystem_exception&) + { + // Don't throw now, it will fail later... + } + + // Copy messages + const std::vector <int> nums = maildirUtils::messageSetToNumberList(msgs); + + 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); + + shared_ptr <utility::file> file = fsf->create(curDirPath / msg.path); + shared_ptr <utility::fileReader> fr = file->getFileReader(); + shared_ptr <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) +{ + shared_ptr <maildirStore> store = m_store.lock(); + + for (std::list <maildirFolder*>::iterator it = store->m_folders.begin() ; + it != 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) +{ + count = 0; + unseen = 0; + + shared_ptr <folderStatus> status = getStatus(); + + count = status->getMessageCount(); + unseen = status->getUnseenCount(); +} + + +shared_ptr <folderStatus> maildirFolder::getStatus() +{ + shared_ptr <maildirStore> store = m_store.lock(); + + const int oldCount = m_messageCount; + + scanFolder(); + + shared_ptr <maildirFolderStatus> status = make_shared <maildirFolderStatus>(); + + status->setMessageCount(m_messageCount); + status->setUnseenCount(m_unreadMessageCount); + + // Notify message count changed (new messages) + if (m_messageCount > oldCount) + { + std::vector <int> nums; + nums.reserve(m_messageCount - oldCount); + + for (int i = oldCount + 1, j = 0 ; i <= m_messageCount ; ++i, ++j) + nums[j] = i; + + shared_ptr <events::messageCountEvent> event = + make_shared <events::messageCountEvent> + (dynamicCast <folder>(shared_from_this()), + events::messageCountEvent::TYPE_ADDED, nums); + + notifyMessageCount(event); + + // Notify folders with the same path + for (std::list <maildirFolder*>::iterator it = store->m_folders.begin() ; + it != 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()); + + shared_ptr <events::messageCountEvent> event = + make_shared <events::messageCountEvent> + (dynamicCast <folder>((*it)->shared_from_this()), + events::messageCountEvent::TYPE_ADDED, nums); + + (*it)->notifyMessageCount(event); + } + } + } + + return status; +} + + +void maildirFolder::expunge() +{ + shared_ptr <maildirStore> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + else if (m_mode == MODE_READ_ONLY) + throw exceptions::illegal_state("Folder is read-only"); + + shared_ptr <utility::fileSystemFactory> fsf = platform::getHandler()->getFileSystemFactory(); + + utility::file::path curDirPath = store->getFormat()-> + folderPathToFileSystemPath(m_path, maildirFormat::CUR_DIRECTORY); + + 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 + { + shared_ptr <utility::file> file = fsf->create(curDirPath / infos.path); + file->remove(); + } + catch (exceptions::filesystem_exception& e) + { + // Ignore (not important) + } + } + } + + if (!nums.empty()) + { + for (std::vector <int>::size_type i = nums.size() ; i != 0 ; --i) + m_messageInfos.erase(m_messageInfos.begin() + (i - 1)); + } + + m_messageCount -= nums.size(); + m_unreadMessageCount -= unreadCount; + + // Notify message expunged + shared_ptr <events::messageCountEvent> event = + make_shared <events::messageCountEvent> + (dynamicCast <folder>(shared_from_this()), + events::messageCountEvent::TYPE_REMOVED, nums); + + notifyMessageCount(event); + + // Notify folders with the same path + for (std::list <maildirFolder*>::iterator it = store->m_folders.begin() ; + it != 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()); + + shared_ptr <events::messageCountEvent> event = + make_shared <events::messageCountEvent> + (dynamicCast <folder>((*it)->shared_from_this()), + events::messageCountEvent::TYPE_REMOVED, nums); + + (*it)->notifyMessageCount(event); + } + } +} + + +shared_ptr <folder> maildirFolder::getParent() +{ + if (m_path.isEmpty()) + return null; + else + return make_shared <maildirFolder>(m_path.getParent(), m_store.lock()); +} + + +shared_ptr <const store> maildirFolder::getStore() const +{ + return m_store.lock(); +} + + +shared_ptr <store> maildirFolder::getStore() +{ + return m_store.lock(); +} + + +void maildirFolder::fetchMessages(std::vector <shared_ptr <message> >& msg, + const fetchAttributes& options, utility::progressListener* progress) +{ + shared_ptr <maildirStore> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + const size_t total = msg.size(); + size_t current = 0; + + if (progress) + progress->start(total); + + shared_ptr <maildirFolder> thisFolder = dynamicCast <maildirFolder>(shared_from_this()); + + for (std::vector <shared_ptr <message> >::iterator it = msg.begin() ; + it != msg.end() ; ++it) + { + dynamicCast <maildirMessage>(*it)->fetch(thisFolder, options); + + if (progress) + progress->progress(++current, total); + } + + if (progress) + progress->stop(total); +} + + +void maildirFolder::fetchMessage(shared_ptr <message> msg, const fetchAttributes& options) +{ + shared_ptr <maildirStore> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + dynamicCast <maildirMessage>(msg)->fetch + (dynamicCast <maildirFolder>(shared_from_this()), options); +} + + +int maildirFolder::getFetchCapabilities() const +{ + return fetchAttributes::ENVELOPE | fetchAttributes::STRUCTURE | + fetchAttributes::CONTENT_INFO | fetchAttributes::FLAGS | + fetchAttributes::SIZE | fetchAttributes::FULL_HEADER | + fetchAttributes::UID | fetchAttributes::IMPORTANCE; +} + + +const utility::file::path maildirFolder::getMessageFSPath(const int number) const +{ + utility::file::path curDirPath = m_store.lock()->getFormat()-> + folderPathToFileSystemPath(m_path, maildirFormat::CUR_DIRECTORY); + + return (curDirPath / m_messageInfos[number - 1].path); +} + + +std::vector <int> maildirFolder::getMessageNumbersStartingOnUID(const message::uid& /* uid */) +{ + throw exceptions::operation_not_supported(); +} + + +} // maildir +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + diff --git a/src/vmime/net/maildir/maildirFolder.hpp b/src/vmime/net/maildir/maildirFolder.hpp new file mode 100644 index 00000000..5cff53fc --- /dev/null +++ b/src/vmime/net/maildir/maildirFolder.hpp @@ -0,0 +1,190 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_MAILDIR_MAILDIRFOLDER_HPP_INCLUDED +#define VMIME_NET_MAILDIR_MAILDIRFOLDER_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + + +#include <vector> +#include <map> + +#include "vmime/types.hpp" + +#include "vmime/net/folder.hpp" + +#include "vmime/utility/file.hpp" + + +namespace vmime { +namespace net { +namespace maildir { + + +class maildirStore; +class maildirMessage; + + +/** maildir folder implementation. + */ + +class VMIME_EXPORT maildirFolder : public folder +{ +private: + + friend class maildirStore; + friend class maildirMessage; + + maildirFolder(const maildirFolder&) : folder() { } + +public: + + maildirFolder(const folder::path& path, shared_ptr <maildirStore> store); + + ~maildirFolder(); + + + int getMode() const; + + int getType(); + + int getFlags(); + + const folder::path::component getName() const; + const folder::path getFullPath() const; + + void open(const int mode, bool failIfModeIsNotAvailable = false); + void close(const bool expunge); + void create(const int type); + + bool exists(); + + void destroy(); + + bool isOpen() const; + + shared_ptr <message> getMessage(const int num); + std::vector <shared_ptr <message> > getMessages(const messageSet& msgs); + + int getMessageCount(); + + shared_ptr <folder> getFolder(const folder::path::component& name); + std::vector <shared_ptr <folder> > getFolders(const bool recursive = false); + + void rename(const folder::path& newPath); + + void deleteMessages(const messageSet& msgs); + + void setMessageFlags(const messageSet& msgs, const int flags, const int mode = message::FLAG_MODE_SET); + + void addMessage(shared_ptr <vmime::message> msg, const int flags = message::FLAG_UNDEFINED, vmime::datetime* date = NULL, utility::progressListener* progress = NULL); + void addMessage(utility::inputStream& is, const size_t size, const int flags = message::FLAG_UNDEFINED, vmime::datetime* date = NULL, utility::progressListener* progress = NULL); + + void copyMessages(const folder::path& dest, const messageSet& msgs); + + void status(int& count, int& unseen); + shared_ptr <folderStatus> getStatus(); + + void expunge(); + + shared_ptr <folder> getParent(); + + shared_ptr <const store> getStore() const; + shared_ptr <store> getStore(); + + + void fetchMessages(std::vector <shared_ptr <message> >& msg, const fetchAttributes& options, utility::progressListener* progress = NULL); + void fetchMessage(shared_ptr <message> msg, const fetchAttributes& options); + + int getFetchCapabilities() const; + + std::vector <int> getMessageNumbersStartingOnUID(const message::uid& uid); + +private: + + void scanFolder(); + + void listFolders(std::vector <shared_ptr <folder> >& list, const bool recursive); + + void registerMessage(maildirMessage* msg); + void unregisterMessage(maildirMessage* msg); + + const utility::file::path getMessageFSPath(const int number) const; + + void onStoreDisconnected(); + + void onClose(); + + void deleteMessagesImpl(const std::vector <int>& nums); + void setMessageFlagsImpl(const std::vector <int>& nums, const int flags, const int mode); + + void copyMessagesImpl(const folder::path& dest, const std::vector <int>& nums); + void copyMessageImpl(const utility::file::path& tmpDirPath, const utility::file::path& curDirPath, const utility::file::path::component& filename, utility::inputStream& is, const size_t size, utility::progressListener* progress); + + void notifyMessagesCopied(const folder::path& dest); + + + weak_ptr <maildirStore> m_store; + + folder::path m_path; + folder::path::component m_name; + + int m_mode; + bool m_open; + + int m_unreadMessageCount; + int m_messageCount; + + // Store information about scanned messages + struct messageInfos + { + enum Type + { + TYPE_CUR, + TYPE_DELETED + }; + + utility::file::path::component path; // filename + Type type; // current location + }; + + std::vector <messageInfos> m_messageInfos; + + // Instanciated message objects + std::vector <maildirMessage*> m_messages; +}; + + +} // maildir +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + +#endif // VMIME_NET_MAILDIR_MAILDIRFOLDER_HPP_INCLUDED diff --git a/src/vmime/net/maildir/maildirFolderStatus.cpp b/src/vmime/net/maildir/maildirFolderStatus.cpp new file mode 100644 index 00000000..9ee84dba --- /dev/null +++ b/src/vmime/net/maildir/maildirFolderStatus.cpp @@ -0,0 +1,88 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + + +#include "vmime/net/maildir/maildirFolderStatus.hpp" + + +namespace vmime { +namespace net { +namespace maildir { + + +maildirFolderStatus::maildirFolderStatus() + : m_count(0), + m_unseen(0) +{ +} + + +maildirFolderStatus::maildirFolderStatus(const maildirFolderStatus& other) + : folderStatus(), + m_count(other.m_count), + m_unseen(other.m_unseen) +{ +} + + +unsigned int maildirFolderStatus::getMessageCount() const +{ + return m_count; +} + + +unsigned int maildirFolderStatus::getUnseenCount() const +{ + return m_unseen; +} + + +void maildirFolderStatus::setMessageCount(const unsigned int count) +{ + m_count = count; +} + + +void maildirFolderStatus::setUnseenCount(const unsigned int unseen) +{ + m_unseen = unseen; +} + + +shared_ptr <folderStatus> maildirFolderStatus::clone() const +{ + return make_shared <maildirFolderStatus>(*this); +} + + +} // maildir +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR diff --git a/src/vmime/net/maildir/maildirFolderStatus.hpp b/src/vmime/net/maildir/maildirFolderStatus.hpp new file mode 100644 index 00000000..155fb20f --- /dev/null +++ b/src/vmime/net/maildir/maildirFolderStatus.hpp @@ -0,0 +1,76 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_MAILDIR_MAILDIRFOLDERSTATUS_HPP_INCLUDED +#define VMIME_NET_MAILDIR_MAILDIRFOLDERSTATUS_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + + +#include "vmime/net/folderStatus.hpp" + + +namespace vmime { +namespace net { +namespace maildir { + + +/** Holds the status of a Maildir folder. + */ + +class VMIME_EXPORT maildirFolderStatus : public folderStatus +{ +public: + + maildirFolderStatus(); + maildirFolderStatus(const maildirFolderStatus& other); + + // Inherited from folderStatus + unsigned int getMessageCount() const; + unsigned int getUnseenCount() const; + + shared_ptr <folderStatus> clone() const; + + + void setMessageCount(const unsigned int count); + void setUnseenCount(const unsigned int unseen); + +private: + + unsigned int m_count; + unsigned int m_unseen; +}; + + +} // maildir +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + +#endif // VMIME_NET_MAILDIR_MAILDIRFOLDERSTATUS_HPP_INCLUDED diff --git a/src/vmime/net/maildir/maildirFormat.cpp b/src/vmime/net/maildir/maildirFormat.cpp new file mode 100644 index 00000000..f7a3c8fe --- /dev/null +++ b/src/vmime/net/maildir/maildirFormat.cpp @@ -0,0 +1,109 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + + +#include "vmime/net/maildir/maildirFormat.hpp" +#include "vmime/net/maildir/maildirStore.hpp" + +#include "vmime/net/maildir/format/kmailMaildirFormat.hpp" +#include "vmime/net/maildir/format/courierMaildirFormat.hpp" + +#include "vmime/utility/file.hpp" + + +namespace vmime { +namespace net { +namespace maildir { + + +const utility::file::path::component maildirFormat::TMP_DIR("tmp", vmime::charset(vmime::charsets::US_ASCII)); +const utility::file::path::component maildirFormat::CUR_DIR("cur", vmime::charset(vmime::charsets::US_ASCII)); +const utility::file::path::component maildirFormat::NEW_DIR("new", vmime::charset(vmime::charsets::US_ASCII)); + + +// +// maildirFormat::context +// + +maildirFormat::context::context(shared_ptr <maildirStore> store) + : m_store(store) +{ +} + + +shared_ptr <maildirStore> maildirFormat::context::getStore() const +{ + return constCast <maildirStore>(m_store.lock()); +} + + +// +// maildirFormat +// + +maildirFormat::maildirFormat(shared_ptr <context> ctx) + : m_context(ctx) +{ +} + + +shared_ptr <maildirFormat::context> maildirFormat::getContext() +{ + return m_context; +} + + +shared_ptr <const maildirFormat::context> maildirFormat::getContext() const +{ + return m_context; +} + + +// static +shared_ptr <maildirFormat> maildirFormat::detect(shared_ptr <maildirStore> store) +{ + shared_ptr <context> ctx = make_shared <context>(store); + + // Try Courier format + shared_ptr <maildirFormat> fmt = make_shared <format::courierMaildirFormat>(ctx); + + if (fmt->supports()) + return fmt; + + // Default is KMail format + return make_shared <format::kmailMaildirFormat>(ctx); +} + + +} // maildir +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + diff --git a/src/vmime/net/maildir/maildirFormat.hpp b/src/vmime/net/maildir/maildirFormat.hpp new file mode 100644 index 00000000..c0daf288 --- /dev/null +++ b/src/vmime/net/maildir/maildirFormat.hpp @@ -0,0 +1,195 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_MAILDIR_FORMAT_MAILDIRFORMAT_HPP_INCLUDED +#define VMIME_NET_MAILDIR_FORMAT_MAILDIRFORMAT_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + + +#include "vmime/net/folder.hpp" + +#include "vmime/utility/file.hpp" +#include "vmime/utility/path.hpp" + + +namespace vmime { +namespace net { +namespace maildir { + + +class maildirStore; + + +/** Interface for an object capable of reading a specific Maildir format. */ + +class VMIME_EXPORT maildirFormat : public object +{ +public: + + class context : public object + { + public: + + context(shared_ptr <maildirStore> store); + + shared_ptr <maildirStore> getStore() const; + + private: + + weak_ptr <maildirStore> m_store; + }; + + + /** Physical directory types. */ + enum DirectoryType + { + ROOT_DIRECTORY, /**< Root directory. */ + NEW_DIRECTORY, /**< Directory containing unread messages. */ + CUR_DIRECTORY, /**< Directory containing messages that have been seen. */ + TMP_DIRECTORY, /**< Temporary directory used for reliable delivery. */ + CONTAINER_DIRECTORY /**< Container for subfolders. */ + }; + + /** Return the name of this Maildir format. + * + * @return format name + */ + virtual const string getName() const = 0; + + /** Create the specified folder. + * + * @param path virtual path of the folder + * @throw exceptions::filesystem_exception, invalid_folder_name + */ + virtual void createFolder(const folder::path& path) = 0; + + /** Destroy the specified folder. + * + * @param path virtual path of the folder + * @throw exceptions::filesystem_exception + */ + virtual void destroyFolder(const folder::path& path) = 0; + + /** Rename the specified folder. + * + * @param oldPath old virtual path of the folder + * @param newPath new virtual path of the folder + * @throw exceptions::filesystem_exception + */ + virtual void renameFolder(const folder::path& oldPath, const folder::path& newPath) = 0; + + /** Test whether the specified folder exists. + * + * @param path virtual path of the folder + * @return true if the folder exists, false otherwise + */ + virtual bool folderExists(const folder::path& path) const = 0; + + /** Test whether the specified folder has subfolders. + * + * @param path virtual path of the folder + * @return true if the folder has at least one subfolder, + * false otherwise + */ + virtual bool folderHasSubfolders(const folder::path& path) const = 0; + + /** Returns the directory which represents the specified + * folder on the file system. + * + * @param path virtual path of the folder + * @param type type of directory to return + * @return corresponding directory on the file system + */ + virtual const utility::file::path folderPathToFileSystemPath + (const folder::path& path, const DirectoryType type) const = 0; + + /** List subfolders in the specified folder. + * + * @param root root folder in which to start the search + * @param recursive if set to true, all the descendant are + * returned; if set to false, only direct children are returned. + * @return list of subfolders + */ + virtual const std::vector <folder::path> listFolders + (const folder::path& root, const bool recursive) const = 0; + + + /** Try to detect the format of the specified Maildir store. + * If the format cannot be detected, a compatible implementation + * will be returned. + * + * @param store of which to detect format + * @return a Maildir format implementation for the specified store + */ + static shared_ptr <maildirFormat> detect(shared_ptr <maildirStore> store); + +protected: + + static const utility::file::path::component TMP_DIR; /**< Ensure reliable delivery (not to be listed). */ + static const utility::file::path::component CUR_DIR; /**< No longer new messages. */ + static const utility::file::path::component NEW_DIR; /**< Unread messages. */ + + + maildirFormat(shared_ptr <context> ctx); + + + /** Returns the current context. + * + * @return current context + */ + shared_ptr <context> getContext(); + + /** Returns the current context (const version). + * + * @return current context + */ + shared_ptr <const context> getContext() const; + + /** Quick checks whether this implementation can read the Maildir + * format in the specified directory. + * + * @return true if the implementation supports the specified + * Maildir, or false otherwise + */ + virtual bool supports() const = 0; + +private: + + shared_ptr <context> m_context; +}; + + +} // maildir +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + +#endif // VMIME_NET_MAILDIR_FORMAT_MAILDIRFORMAT_HPP_INCLUDED + diff --git a/src/vmime/net/maildir/maildirMessage.cpp b/src/vmime/net/maildir/maildirMessage.cpp new file mode 100644 index 00000000..a14f067e --- /dev/null +++ b/src/vmime/net/maildir/maildirMessage.cpp @@ -0,0 +1,369 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + + +#include "vmime/net/maildir/maildirMessage.hpp" +#include "vmime/net/maildir/maildirMessagePart.hpp" +#include "vmime/net/maildir/maildirMessageStructure.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/platform.hpp" + +#include "vmime/utility/outputStreamAdapter.hpp" +#include "vmime/utility/stringUtils.hpp" + + +namespace vmime { +namespace net { +namespace maildir { + + +maildirMessage::maildirMessage(shared_ptr <maildirFolder> folder, const int num) + : m_folder(folder), m_num(num), m_size(-1), m_flags(FLAG_UNDEFINED), + m_expunged(false), m_structure(null) +{ + folder->registerMessage(this); +} + + +maildirMessage::~maildirMessage() +{ + shared_ptr <maildirFolder> folder = m_folder.lock(); + + if (folder) + folder->unregisterMessage(this); +} + + +void maildirMessage::onFolderClosed() +{ + m_folder.reset(); +} + + +int maildirMessage::getNumber() const +{ + return (m_num); +} + + +const message::uid maildirMessage::getUID() const +{ + return (m_uid); +} + + +size_t maildirMessage::getSize() const +{ + if (m_size == static_cast <size_t>(-1)) + throw exceptions::unfetched_object(); + + return (m_size); +} + + +bool maildirMessage::isExpunged() const +{ + return (m_expunged); +} + + +shared_ptr <const messageStructure> maildirMessage::getStructure() const +{ + if (m_structure == NULL) + throw exceptions::unfetched_object(); + + return m_structure; +} + + +shared_ptr <messageStructure> maildirMessage::getStructure() +{ + if (m_structure == NULL) + throw exceptions::unfetched_object(); + + return m_structure; +} + + +shared_ptr <const header> maildirMessage::getHeader() const +{ + if (m_header == NULL) + throw exceptions::unfetched_object(); + + return (m_header); +} + + +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) +{ + shared_ptr <maildirFolder> folder = m_folder.lock(); + + if (!folder) + throw exceptions::folder_not_found(); + + folder->setMessageFlags(messageSet::byNumber(m_num), flags, mode); +} + + +void maildirMessage::extract(utility::outputStream& os, + utility::progressListener* progress, const size_t start, + const size_t length, const bool peek) const +{ + extractImpl(os, progress, 0, m_size, start, length, peek); +} + + +void maildirMessage::extractPart(shared_ptr <const messagePart> p, utility::outputStream& os, + utility::progressListener* progress, const size_t start, + const size_t length, const bool peek) const +{ + shared_ptr <const maildirMessagePart> mp = dynamicCast <const maildirMessagePart>(p); + + extractImpl(os, progress, mp->getBodyParsedOffset(), mp->getBodyParsedLength(), + start, length, peek); +} + + +void maildirMessage::extractImpl(utility::outputStream& os, utility::progressListener* progress, + const size_t start, const size_t length, const size_t partialStart, const size_t partialLength, + const bool /* peek */) const +{ + shared_ptr <const maildirFolder> folder = m_folder.lock(); + + shared_ptr <utility::fileSystemFactory> fsf = platform::getHandler()->getFileSystemFactory(); + + const utility::file::path path = folder->getMessageFSPath(m_num); + shared_ptr <utility::file> file = fsf->create(path); + + shared_ptr <utility::fileReader> reader = file->getFileReader(); + shared_ptr <utility::inputStream> is = reader->getInputStream(); + + is->skip(start + partialStart); + + byte_t buffer[8192]; + size_t remaining = (partialLength == static_cast <size_t>(-1) + ? length : std::min(partialLength, length)); + + const size_t total = remaining; + size_t current = 0; + + if (progress) + progress->start(total); + + while (!is->eof() && remaining > 0) + { + const size_t 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(shared_ptr <messagePart> p) +{ + shared_ptr <maildirFolder> folder = m_folder.lock(); + + shared_ptr <maildirMessagePart> mp = dynamicCast <maildirMessagePart>(p); + + shared_ptr <utility::fileSystemFactory> fsf = platform::getHandler()->getFileSystemFactory(); + + const utility::file::path path = folder->getMessageFSPath(m_num); + shared_ptr <utility::file> file = fsf->create(path); + + shared_ptr <utility::fileReader> reader = file->getFileReader(); + shared_ptr <utility::inputStream> is = reader->getInputStream(); + + is->skip(mp->getHeaderParsedOffset()); + + byte_t buffer[1024]; + size_t remaining = mp->getHeaderParsedLength(); + + string contents; + contents.reserve(remaining); + + while (!is->eof() && remaining > 0) + { + const size_t read = is->read(buffer, std::min(remaining, sizeof(buffer))); + + remaining -= read; + + vmime::utility::stringUtils::appendBytesToString(contents, buffer, read); + } + + mp->getOrCreateHeader().parse(contents); +} + + +void maildirMessage::fetch(shared_ptr <maildirFolder> msgFolder, const fetchAttributes& options) +{ + shared_ptr <maildirFolder> folder = m_folder.lock(); + + if (folder != msgFolder) + throw exceptions::folder_not_found(); + + shared_ptr <utility::fileSystemFactory> fsf = platform::getHandler()->getFileSystemFactory(); + + const utility::file::path path = folder->getMessageFSPath(m_num); + shared_ptr <utility::file> file = fsf->create(path); + + if (options.has(fetchAttributes::FLAGS)) + m_flags = maildirUtils::extractFlags(path.getLastComponent()); + + if (options.has(fetchAttributes::SIZE)) + m_size = file->getLength(); + + if (options.has(fetchAttributes::UID)) + m_uid = maildirUtils::extractId(path.getLastComponent()).getBuffer(); + + if (options.has(fetchAttributes::ENVELOPE | fetchAttributes::CONTENT_INFO | + fetchAttributes::FULL_HEADER | fetchAttributes::STRUCTURE | + fetchAttributes::IMPORTANCE)) + { + string contents; + + shared_ptr <utility::fileReader> reader = file->getFileReader(); + shared_ptr <utility::inputStream> is = reader->getInputStream(); + + // Need whole message contents for structure + if (options.has(fetchAttributes::STRUCTURE)) + { + byte_t buffer[16384]; + + contents.reserve(file->getLength()); + + while (!is->eof()) + { + const size_t read = is->read(buffer, sizeof(buffer)); + vmime::utility::stringUtils::appendBytesToString(contents, buffer, read); + } + } + // Need only header + else + { + byte_t buffer[1024]; + + contents.reserve(4096); + + while (!is->eof()) + { + const size_t read = is->read(buffer, sizeof(buffer)); + vmime::utility::stringUtils::appendBytesToString(contents, buffer, read); + + const size_t sep1 = contents.rfind("\r\n\r\n"); + const size_t 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.has(fetchAttributes::STRUCTURE)) + { + m_structure = make_shared <maildirMessageStructure>(shared_ptr <maildirMessagePart>(), msg); + } + + // Extract some header fields or whole header + if (options.has(fetchAttributes::ENVELOPE | + fetchAttributes::CONTENT_INFO | + fetchAttributes::FULL_HEADER | + fetchAttributes::IMPORTANCE)) + { + getOrCreateHeader()->copyFrom(*(msg.getHeader())); + } + } +} + + +shared_ptr <header> maildirMessage::getOrCreateHeader() +{ + if (m_header != NULL) + return (m_header); + else + return (m_header = make_shared <header>()); +} + + +shared_ptr <vmime::message> maildirMessage::getParsedMessage() +{ + std::ostringstream oss; + utility::outputStreamAdapter os(oss); + + extract(os); + + shared_ptr <vmime::message> msg = make_shared <vmime::message>(); + msg->parse(oss.str()); + + return msg; +} + + +} // maildir +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + diff --git a/src/vmime/net/maildir/maildirMessage.hpp b/src/vmime/net/maildir/maildirMessage.hpp new file mode 100644 index 00000000..7480d49c --- /dev/null +++ b/src/vmime/net/maildir/maildirMessage.hpp @@ -0,0 +1,116 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_MAILDIR_MAILDIRMESSAGE_HPP_INCLUDED +#define VMIME_NET_MAILDIR_MAILDIRMESSAGE_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + + +#include "vmime/net/message.hpp" +#include "vmime/net/folder.hpp" + + +namespace vmime { +namespace net { +namespace maildir { + + +class maildirFolder; + + +/** maildir message implementation. + */ + +class VMIME_EXPORT maildirMessage : public message +{ + friend class maildirFolder; + + maildirMessage(const maildirMessage&) : message() { } + +public: + + maildirMessage(shared_ptr <maildirFolder> folder, const int num); + + ~maildirMessage(); + + + int getNumber() const; + + const uid getUID() const; + + size_t getSize() const; + + bool isExpunged() const; + + shared_ptr <const messageStructure> getStructure() const; + shared_ptr <messageStructure> getStructure(); + + shared_ptr <const header> getHeader() const; + + int getFlags() const; + void setFlags(const int flags, const int mode = FLAG_MODE_SET); + + void extract(utility::outputStream& os, utility::progressListener* progress = NULL, const size_t start = 0, const size_t length = -1, const bool peek = false) const; + void extractPart(shared_ptr <const messagePart> p, utility::outputStream& os, utility::progressListener* progress = NULL, const size_t start = 0, const size_t length = -1, const bool peek = false) const; + + void fetchPartHeader(shared_ptr <messagePart> p); + + shared_ptr <vmime::message> getParsedMessage(); + +private: + + void fetch(shared_ptr <maildirFolder> folder, const fetchAttributes& options); + + void onFolderClosed(); + + shared_ptr <header> getOrCreateHeader(); + + void extractImpl(utility::outputStream& os, utility::progressListener* progress, const size_t start, const size_t length, const size_t partialStart, const size_t partialLength, const bool peek) const; + + + weak_ptr <maildirFolder> m_folder; + + int m_num; + size_t m_size; + int m_flags; + bool m_expunged; + uid m_uid; + + shared_ptr <header> m_header; + shared_ptr <messageStructure> m_structure; +}; + + +} // maildir +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + +#endif // VMIME_NET_MAILDIR_MAILDIRMESSAGE_HPP_INCLUDED diff --git a/src/vmime/net/maildir/maildirMessagePart.cpp b/src/vmime/net/maildir/maildirMessagePart.cpp new file mode 100644 index 00000000..6ae085c9 --- /dev/null +++ b/src/vmime/net/maildir/maildirMessagePart.cpp @@ -0,0 +1,155 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + + +#include "vmime/net/maildir/maildirMessagePart.hpp" +#include "vmime/net/maildir/maildirMessageStructure.hpp" + + +namespace vmime { +namespace net { +namespace maildir { + + +maildirMessagePart::maildirMessagePart(shared_ptr <maildirMessagePart> parent, const int number, const bodyPart& part) + : m_parent(parent), m_header(null), m_number(number) +{ + 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(); +} + + +maildirMessagePart::~maildirMessagePart() +{ +} + + +void maildirMessagePart::initStructure(const bodyPart& part) +{ + if (part.getBody()->getPartList().size() == 0) + m_structure = null; + else + { + m_structure = make_shared <maildirMessageStructure> + (dynamicCast <maildirMessagePart>(shared_from_this()), + part.getBody()->getPartList()); + } +} + + +shared_ptr <const messageStructure> maildirMessagePart::getStructure() const +{ + if (m_structure != NULL) + return m_structure; + else + return maildirMessageStructure::emptyStructure(); +} + + +shared_ptr <messageStructure> maildirMessagePart::getStructure() +{ + if (m_structure != NULL) + return m_structure; + else + return maildirMessageStructure::emptyStructure(); +} + + +const mediaType& maildirMessagePart::getType() const +{ + return m_mediaType; +} + + +size_t maildirMessagePart::getSize() const +{ + return m_size; +} + + +int maildirMessagePart::getNumber() const +{ + return m_number; +} + + +shared_ptr <const header> maildirMessagePart::getHeader() const +{ + if (m_header == NULL) + throw exceptions::unfetched_object(); + else + return m_header; +} + + +header& maildirMessagePart::getOrCreateHeader() +{ + if (m_header != NULL) + return *m_header; + else + return *(m_header = make_shared <header>()); +} + + +size_t maildirMessagePart::getHeaderParsedOffset() const +{ + return m_headerParsedOffset; +} + + +size_t maildirMessagePart::getHeaderParsedLength() const +{ + return m_headerParsedLength; +} + + +size_t maildirMessagePart::getBodyParsedOffset() const +{ + return m_bodyParsedOffset; +} + + +size_t maildirMessagePart::getBodyParsedLength() const +{ + return m_bodyParsedLength; +} + + +} // maildir +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR diff --git a/src/vmime/net/maildir/maildirMessagePart.hpp b/src/vmime/net/maildir/maildirMessagePart.hpp new file mode 100644 index 00000000..3a4be0f3 --- /dev/null +++ b/src/vmime/net/maildir/maildirMessagePart.hpp @@ -0,0 +1,99 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_MAILDIR_MAILDIRMESSAGEPART_HPP_INCLUDED +#define VMIME_NET_MAILDIR_MAILDIRMESSAGEPART_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + + +#include "vmime/net/message.hpp" + + +namespace vmime { +namespace net { +namespace maildir { + + +class maildirMessageStructure; + + +class maildirMessagePart : public messagePart +{ +public: + + maildirMessagePart(shared_ptr <maildirMessagePart> parent, const int number, const bodyPart& part); + ~maildirMessagePart(); + + + shared_ptr <const messageStructure> getStructure() const; + shared_ptr <messageStructure> getStructure(); + + weak_ptr <const maildirMessagePart> getParent() const { return (m_parent); } + + const mediaType& getType() const; + size_t getSize() const; + int getNumber() const; + + shared_ptr <const header> getHeader() const; + + header& getOrCreateHeader(); + + size_t getHeaderParsedOffset() const; + size_t getHeaderParsedLength() const; + + size_t getBodyParsedOffset() const; + size_t getBodyParsedLength() const; + + void initStructure(const bodyPart& part); + +private: + + shared_ptr <maildirMessageStructure> m_structure; + weak_ptr <maildirMessagePart> m_parent; + shared_ptr <header> m_header; + + int m_number; + size_t m_size; + mediaType m_mediaType; + + size_t m_headerParsedOffset; + size_t m_headerParsedLength; + + size_t m_bodyParsedOffset; + size_t m_bodyParsedLength; +}; + + +} // maildir +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + +#endif // VMIME_NET_MAILDIR_MAILDIRMESSAGEPART_HPP_INCLUDED diff --git a/src/vmime/net/maildir/maildirMessageStructure.cpp b/src/vmime/net/maildir/maildirMessageStructure.cpp new file mode 100644 index 00000000..f3b7cf59 --- /dev/null +++ b/src/vmime/net/maildir/maildirMessageStructure.cpp @@ -0,0 +1,98 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + + +#include "vmime/net/maildir/maildirMessageStructure.hpp" +#include "vmime/net/maildir/maildirMessagePart.hpp" + + +namespace vmime { +namespace net { +namespace maildir { + + +shared_ptr <maildirMessageStructure> maildirMessageStructure::m_emptyStructure = make_shared <maildirMessageStructure>(); + + +maildirMessageStructure::maildirMessageStructure() +{ +} + + +maildirMessageStructure::maildirMessageStructure(shared_ptr <maildirMessagePart> parent, const bodyPart& part) +{ + shared_ptr <maildirMessagePart> mpart = make_shared <maildirMessagePart>(parent, 0, part); + mpart->initStructure(part); + + m_parts.push_back(mpart); +} + + +maildirMessageStructure::maildirMessageStructure(shared_ptr <maildirMessagePart> parent, const std::vector <shared_ptr <const vmime::bodyPart> >& list) +{ + for (unsigned int i = 0 ; i < list.size() ; ++i) + { + shared_ptr <maildirMessagePart> mpart = make_shared <maildirMessagePart>(parent, i, *list[i]); + mpart->initStructure(*list[i]); + + m_parts.push_back(mpart); + } +} + + +shared_ptr <const messagePart> maildirMessageStructure::getPartAt(const size_t x) const +{ + return m_parts[x]; +} + + +shared_ptr <messagePart> maildirMessageStructure::getPartAt(const size_t x) +{ + return m_parts[x]; +} + + +size_t maildirMessageStructure::getPartCount() const +{ + return m_parts.size(); +} + + +// static +shared_ptr <maildirMessageStructure> maildirMessageStructure::emptyStructure() +{ + return m_emptyStructure; +} + + +} // maildir +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR diff --git a/src/vmime/net/maildir/maildirMessageStructure.hpp b/src/vmime/net/maildir/maildirMessageStructure.hpp new file mode 100644 index 00000000..a43fc15c --- /dev/null +++ b/src/vmime/net/maildir/maildirMessageStructure.hpp @@ -0,0 +1,76 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_MAILDIR_MAILDIRMESSAGESTRUCTURE_HPP_INCLUDED +#define VMIME_NET_MAILDIR_MAILDIRMESSAGESTRUCTURE_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + + +#include "vmime/net/message.hpp" + + +namespace vmime { +namespace net { +namespace maildir { + + +class maildirMessagePart; + + +class maildirMessageStructure : public messageStructure +{ +public: + + maildirMessageStructure(); + maildirMessageStructure(shared_ptr <maildirMessagePart> parent, const bodyPart& part); + maildirMessageStructure(shared_ptr <maildirMessagePart> parent, const std::vector <shared_ptr <const vmime::bodyPart> >& list); + + + shared_ptr <const messagePart> getPartAt(const size_t x) const; + shared_ptr <messagePart> getPartAt(const size_t x); + + size_t getPartCount() const; + + static shared_ptr <maildirMessageStructure> emptyStructure(); + +private: + + static shared_ptr <maildirMessageStructure> m_emptyStructure; + + std::vector <shared_ptr <maildirMessagePart> > m_parts; +}; + + +} // maildir +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + +#endif // VMIME_NET_MAILDIR_MAILDIRMESSAGESTRUCTURE_HPP_INCLUDED diff --git a/src/vmime/net/maildir/maildirServiceInfos.cpp b/src/vmime/net/maildir/maildirServiceInfos.cpp new file mode 100644 index 00000000..974a0c21 --- /dev/null +++ b/src/vmime/net/maildir/maildirServiceInfos.cpp @@ -0,0 +1,77 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + + +#include "vmime/net/maildir/maildirServiceInfos.hpp" + + +namespace vmime { +namespace net { +namespace maildir { + + +maildirServiceInfos::maildirServiceInfos() +{ +} + + +const string maildirServiceInfos::getPropertyPrefix() const +{ + return "store.maildir."; +} + + +const maildirServiceInfos::props& maildirServiceInfos::getProperties() const +{ + static props maildirProps = + { + property(serviceInfos::property::SERVER_ROOTPATH, serviceInfos::property::FLAG_REQUIRED) + }; + + return maildirProps; +} + + +const std::vector <serviceInfos::property> maildirServiceInfos::getAvailableProperties() const +{ + std::vector <property> list; + const props& p = getProperties(); + + list.push_back(p.PROPERTY_SERVER_ROOTPATH); + + return list; +} + + +} // maildir +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + diff --git a/src/vmime/net/maildir/maildirServiceInfos.hpp b/src/vmime/net/maildir/maildirServiceInfos.hpp new file mode 100644 index 00000000..70ddc6dc --- /dev/null +++ b/src/vmime/net/maildir/maildirServiceInfos.hpp @@ -0,0 +1,71 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_MAILDIR_MAILDIRSERVICEINFOS_HPP_INCLUDED +#define VMIME_NET_MAILDIR_MAILDIRSERVICEINFOS_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + + +#include "vmime/net/serviceInfos.hpp" + + +namespace vmime { +namespace net { +namespace maildir { + + +/** Information about maildir service. + */ + +class VMIME_EXPORT maildirServiceInfos : public serviceInfos +{ +public: + + maildirServiceInfos(); + + struct props + { + serviceInfos::property PROPERTY_SERVER_ROOTPATH; + }; + + const props& getProperties() const; + + const string getPropertyPrefix() const; + const std::vector <serviceInfos::property> getAvailableProperties() const; +}; + + +} // maildir +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + +#endif // VMIME_NET_MAILDIR_MAILDIRSERVICEINFOS_HPP_INCLUDED + diff --git a/src/vmime/net/maildir/maildirStore.cpp b/src/vmime/net/maildir/maildirStore.cpp new file mode 100644 index 00000000..87e733e2 --- /dev/null +++ b/src/vmime/net/maildir/maildirStore.cpp @@ -0,0 +1,272 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + + +#include "vmime/net/maildir/maildirStore.hpp" + +#include "vmime/net/maildir/maildirFolder.hpp" +#include "vmime/net/maildir/maildirFormat.hpp" + +#include "vmime/exception.hpp" +#include "vmime/platform.hpp" + +#include "vmime/net/defaultConnectionInfos.hpp" + + +// Helpers for service properties +#define GET_PROPERTY(type, prop) \ + (getInfos().getPropertyValue <type>(getSession(), \ + dynamic_cast <const maildirServiceInfos&>(getInfos()).getProperties().prop)) +#define HAS_PROPERTY(prop) \ + (getInfos().hasProperty(getSession(), \ + dynamic_cast <const maildirServiceInfos&>(getInfos()).getProperties().prop)) + + +namespace vmime { +namespace net { +namespace maildir { + + +maildirStore::maildirStore(shared_ptr <session> sess, shared_ptr <security::authenticator> auth) + : store(sess, getInfosInstance(), auth), m_connected(false) +{ +} + + +maildirStore::~maildirStore() +{ + try + { + if (isConnected()) + disconnect(); + } + catch (vmime::exception&) + { + // Ignore + } +} + + +const string maildirStore::getProtocolName() const +{ + return "maildir"; +} + + +shared_ptr <folder> maildirStore::getRootFolder() +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return make_shared <maildirFolder> + (folder::path(), + dynamicCast <maildirStore>(shared_from_this())); +} + + +shared_ptr <folder> maildirStore::getDefaultFolder() +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return make_shared <maildirFolder> + (folder::path::component("inbox"), + dynamicCast <maildirStore>(shared_from_this())); +} + + +shared_ptr <folder> maildirStore::getFolder(const folder::path& path) +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return make_shared <maildirFolder> + (path, dynamicCast <maildirStore>(shared_from_this())); +} + + +bool maildirStore::isValidFolderName(const folder::path::component& name) const +{ + if (!platform::getHandler()->getFileSystemFactory()->isValidPathComponent(name)) + return false; + + const string& buf = name.getBuffer(); + + // Name cannot start/end with spaces + if (utility::stringUtils::trim(buf) != buf) + return false; + + // Name cannot start with '.' + const size_t 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 + shared_ptr <utility::fileSystemFactory> fsf = platform::getHandler()->getFileSystemFactory(); + + m_fsPath = fsf->stringToPath(GET_PROPERTY(string, PROPERTY_SERVER_ROOTPATH)); + + shared_ptr <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_format = maildirFormat::detect(dynamicCast <maildirStore>(shared_from_this())); + + m_connected = true; +} + + +bool maildirStore::isConnected() const +{ + return (m_connected); +} + + +bool maildirStore::isSecuredConnection() const +{ + return false; +} + + +shared_ptr <connectionInfos> maildirStore::getConnectionInfos() const +{ + return make_shared <defaultConnectionInfos>("localhost", static_cast <port_t>(0)); +} + + +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. +} + + +shared_ptr <maildirFormat> maildirStore::getFormat() +{ + return m_format; +} + + +shared_ptr <const maildirFormat> maildirStore::getFormat() const +{ + return m_format; +} + + +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); +} + + +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 + +maildirServiceInfos maildirStore::sm_infos; + + +const serviceInfos& maildirStore::getInfosInstance() +{ + return sm_infos; +} + + +const serviceInfos& maildirStore::getInfos() const +{ + return sm_infos; +} + + +} // maildir +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + diff --git a/src/vmime/net/maildir/maildirStore.hpp b/src/vmime/net/maildir/maildirStore.hpp new file mode 100644 index 00000000..efadfdfe --- /dev/null +++ b/src/vmime/net/maildir/maildirStore.hpp @@ -0,0 +1,120 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_MAILDIR_MAILDIRSTORE_HPP_INCLUDED +#define VMIME_NET_MAILDIR_MAILDIRSTORE_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + + +#include "vmime/net/store.hpp" +#include "vmime/net/socket.hpp" +#include "vmime/net/folder.hpp" + +#include "vmime/net/maildir/maildirFormat.hpp" +#include "vmime/net/maildir/maildirServiceInfos.hpp" + +#include "vmime/utility/file.hpp" + +#include <ostream> + + +namespace vmime { +namespace net { +namespace maildir { + + +class maildirFolder; + + +/** maildir store service. + */ + +class VMIME_EXPORT maildirStore : public store +{ + friend class maildirFolder; + +public: + + maildirStore(shared_ptr <session> sess, shared_ptr <security::authenticator> auth); + ~maildirStore(); + + const string getProtocolName() const; + + shared_ptr <folder> getDefaultFolder(); + shared_ptr <folder> getRootFolder(); + shared_ptr <folder> getFolder(const folder::path& path); + + bool isValidFolderName(const folder::path::component& name) const; + + static const serviceInfos& getInfosInstance(); + const serviceInfos& getInfos() const; + + void connect(); + bool isConnected() const; + void disconnect(); + + void noop(); + + const utility::path& getFileSystemPath() const; + + int getCapabilities() const; + + bool isSecuredConnection() const; + shared_ptr <connectionInfos> getConnectionInfos() const; + + shared_ptr <maildirFormat> getFormat(); + shared_ptr <const maildirFormat> getFormat() const; + +private: + + void registerFolder(maildirFolder* folder); + void unregisterFolder(maildirFolder* folder); + + + std::list <maildirFolder*> m_folders; + + shared_ptr <maildirFormat> m_format; + + bool m_connected; + + utility::path m_fsPath; + + + // Service infos + static maildirServiceInfos sm_infos; +}; + + +} // maildir +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + +#endif // VMIME_NET_MAILDIR_MAILDIRSTORE_HPP_INCLUDED diff --git a/src/vmime/net/maildir/maildirUtils.cpp b/src/vmime/net/maildir/maildirUtils.cpp new file mode 100644 index 00000000..77aac715 --- /dev/null +++ b/src/vmime/net/maildir/maildirUtils.cpp @@ -0,0 +1,273 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + + +#include "vmime/net/maildir/maildirUtils.hpp" +#include "vmime/net/maildir/maildirStore.hpp" + +#include "vmime/utility/random.hpp" +#include "vmime/platform.hpp" + +#include "vmime/exception.hpp" + + +namespace vmime { +namespace net { +namespace maildir { + + +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); +} + + +// NOTE ABOUT ID/FLAGS SEPARATOR +// ----------------------------- +// In the maildir specification, the character ':' is used to separate +// the unique identifier and the message flags. +// +// On Windows (and particularly FAT file systems), ':' is not allowed +// in a filename, so we use a dash ('-') instead. This is the solution +// used by Mutt/Win32, so we also use it here. +// +// To be compatible between implementations, we check for both +// characters when reading file names. + + +const utility::file::path::component maildirUtils::extractId + (const utility::file::path::component& filename) +{ + size_t sep = filename.getBuffer().rfind(':'); // try colon + + if (sep == string::npos) + { + sep = filename.getBuffer().rfind('-'); // try dash (Windows) + if (sep == string::npos) return (filename); + } + + return (utility::path::component + (string(filename.getBuffer().begin(), filename.getBuffer().begin() + sep))); +} + + +int maildirUtils::extractFlags(const utility::file::path::component& comp) +{ + size_t sep = comp.getBuffer().rfind(':'); // try colon + + if (sep == string::npos) + { + sep = comp.getBuffer().rfind('-'); // try dash (Windows) + if (sep == string::npos) return 0; + } + + const string flagsString(comp.getBuffer().begin() + sep + 1, comp.getBuffer().end()); + const size_t count = flagsString.length(); + + int flags = 0; + + for (size_t 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; + case 'D': case 'd': flags |= message::FLAG_DRAFT; 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"; + if (flags & message::FLAG_DRAFT) str += "D"; + + return (utility::file::path::component(str)); +} + + +const utility::file::path::component maildirUtils::buildFilename + (const utility::file::path::component& id, const int flags) +{ + if (flags == message::FLAG_RECENT) + return id; + else + return (buildFilename(id, buildFlags(flags))); +} + + +const utility::file::path::component maildirUtils::buildFilename + (const utility::file::path::component& id, + const utility::file::path::component& flags) +{ +#if VMIME_PLATFORM_IS_WINDOWS + static const char DELIMITER[] = "-"; +#else + static const char DELIMITER[] = ":"; +#endif + + return utility::path::component(id.getBuffer() + DELIMITER + flags.getBuffer()); +} + + +const utility::file::path::component maildirUtils::generateId() +{ + std::ostringstream oss; + oss.imbue(std::locale::classic()); + + oss << utility::random::getTime(); + oss << "."; + oss << utility::random::getProcess(); + oss << "."; + oss << utility::random::getString(6); + oss << "."; + oss << platform::getHandler()->getHostName(); + + return (utility::file::path::component(oss.str())); +} + + +void maildirUtils::recursiveFSDelete(shared_ptr <utility::file> dir) +{ + shared_ptr <utility::fileIterator> files = dir->getFiles(); + + // First, delete files and subdirectories in this directory + while (files->hasMoreElements()) + { + shared_ptr <utility::file> file = files->nextElement(); + + if (file->isDirectory()) + { + maildirUtils::recursiveFSDelete(file); + } + else + { + try + { + file->remove(); + } + catch (exceptions::filesystem_exception&) + { + // Ignore + } + } + } + + // Then, delete this (empty) directory + try + { + dir->remove(); + } + catch (exceptions::filesystem_exception&) + { + // Ignore + } +} + + + +class maildirMessageSetEnumerator : public messageSetEnumerator +{ +public: + + void enumerateNumberMessageRange(const vmime::net::numberMessageRange& range) + { + for (int i = range.getFirst(), last = range.getLast() ; i <= last ; ++i) + list.push_back(i); + } + + void enumerateUIDMessageRange(const vmime::net::UIDMessageRange& /* range */) + { + // Not supported + } + +public: + + std::vector <int> list; +}; + + +// static +const std::vector <int> maildirUtils::messageSetToNumberList(const messageSet& msgs) +{ + maildirMessageSetEnumerator en; + msgs.enumerate(en); + + return en.list; +} + + + +// +// messageIdComparator +// + +maildirUtils::messageIdComparator::messageIdComparator + (const utility::file::path::component& comp) + : m_comp(maildirUtils::extractId(comp)) +{ +} + + +bool maildirUtils::messageIdComparator::operator() + (const utility::file::path::component& other) const +{ + return (m_comp == maildirUtils::extractId(other)); +} + + +} // maildir +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + diff --git a/src/vmime/net/maildir/maildirUtils.hpp b/src/vmime/net/maildir/maildirUtils.hpp new file mode 100644 index 00000000..82deefbb --- /dev/null +++ b/src/vmime/net/maildir/maildirUtils.hpp @@ -0,0 +1,151 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_MAILDIR_MAILDIRUTILS_HPP_INCLUDED +#define VMIME_NET_MAILDIR_MAILDIRUTILS_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + + +#include "vmime/utility/file.hpp" +#include "vmime/utility/path.hpp" + +#include "vmime/net/messageSet.hpp" + + +namespace vmime { +namespace net { +namespace maildir { + + +class maildirStore; + + +/** Miscellaneous helpers functions for maildir messaging system. + */ + +class VMIME_EXPORT maildirUtils +{ +public: + + /** Comparator for message filenames, based only on the + * unique identifier part of the filename. + */ + class messageIdComparator + { + public: + + messageIdComparator(const utility::file::path::component& comp); + + bool operator()(const utility::file::path::component& other) const; + + private: + + const utility::file::path::component m_comp; + }; + + /** Test whether the specified file-system object is a message. + * + * @param file reference to a file-system object + * @return true if the specified object is a message file, + * false otherwise + */ + static bool isMessageFile(const utility::file& file); + + /** Extract the unique identifier part of the message filename. + * Eg: for the filename "1071577232.28549.m03s:2,RS", it will + * return "1071577232.28549.m03s". + * + * @param filename filename part + * @return part of the filename that corresponds to the unique + * identifier of the message + */ + static const utility::file::path::component extractId(const utility::file::path::component& filename); + + /** Extract message flags from the specified message filename. + * Eg: for the filename "1071577232.28549.m03s:2,RS", it will + * return (message::FLAG_SEEN | message::FLAG_REPLIED). + * + * @param comp filename part + * @return message flags extracted from the specified filename + */ + static int extractFlags(const utility::file::path::component& comp); + + /** Return a string representing the specified message flags. + * Eg: for (message::FLAG_SEEN | message::FLAG_REPLIED), it will + * return "RS". + * + * @param flags set of flags + * @return message flags in a string representation + */ + static const utility::file::path::component buildFlags(const int flags); + + /** Build a filename with the specified id and flags. + * + * @param id id part of the filename + * @param flags flags part of the filename + * @return message filename + */ + static const utility::file::path::component buildFilename(const utility::file::path::component& id, const utility::file::path::component& flags); + + /** Build a filename with the specified id and flags. + * + * @param id id part of the filename + * @param flags set of flags + * @return message filename + */ + static const utility::file::path::component buildFilename(const utility::file::path::component& id, const int flags); + + /** Generate a new unique message identifier. + * + * @return unique message id + */ + static const utility::file::path::component generateId(); + + /** Recursively delete a directory on the file system. + * + * @param dir directory to delete + */ + static void recursiveFSDelete(shared_ptr <utility::file> dir); + + /** Returns a list of message numbers given a message set. + * + * @param msgs message set + * @return list of message numbers + */ + static const std::vector <int> messageSetToNumberList(const messageSet& msgs); +}; + + +} // maildir +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_MAILDIR + +#endif // VMIME_NET_MAILDIR_MAILDIRUTILS_HPP_INCLUDED diff --git a/src/vmime/net/message.cpp b/src/vmime/net/message.cpp new file mode 100644 index 00000000..6765e73c --- /dev/null +++ b/src/vmime/net/message.cpp @@ -0,0 +1,150 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#include "vmime/net/message.hpp" + +#include <sstream> + + +namespace vmime { +namespace net { + + +shared_ptr <const messagePart> messagePart::getPartAt(const size_t pos) const +{ + return getStructure()->getPartAt(pos); +} + + +shared_ptr <messagePart> messagePart::getPartAt(const size_t pos) +{ + return getStructure()->getPartAt(pos); +} + + +size_t messagePart::getPartCount() const +{ + return getStructure()->getPartCount(); +} + + + +// message::uid + + +message::uid::uid() +{ +} + + +message::uid::uid(const string& uid) + : m_str(uid) +{ +} + + +message::uid::uid(const unsigned long uid) +{ + std::ostringstream oss; + oss.imbue(std::locale::classic()); + oss << uid; + + m_str = oss.str(); +} + + +message::uid::uid(const char* uid) + : m_str(uid) +{ +} + + +message::uid::uid(const uid& other) +{ + m_str = other.m_str; +} + + +message::uid& message::uid::operator=(const uid& other) +{ + m_str = other.m_str; + return *this; +} + + +message::uid& message::uid::operator=(const string& uid) +{ + m_str = uid; + return *this; +} + + +message::uid& message::uid::operator=(const unsigned long uid) +{ + std::ostringstream oss; + oss.imbue(std::locale::classic()); + oss << uid; + + m_str = oss.str(); + + return *this; +} + + +message::uid::operator string() const +{ + return m_str; +} + + +bool message::uid::empty() const +{ + return m_str.empty(); +} + + +bool message::uid::operator==(const uid& other) const +{ + return m_str == other.m_str; +} + + +std::ostream& operator<<(std::ostream& os, const message::uid& uid) +{ + os << static_cast <string>(uid); + return os; +} + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + diff --git a/src/vmime/net/message.hpp b/src/vmime/net/message.hpp new file mode 100644 index 00000000..5bb62c53 --- /dev/null +++ b/src/vmime/net/message.hpp @@ -0,0 +1,355 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_MESSAGE_HPP_INCLUDED +#define VMIME_NET_MESSAGE_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#include "vmime/header.hpp" +#include "vmime/mediaType.hpp" + +#include "vmime/utility/progressListener.hpp" +#include "vmime/utility/stream.hpp" + +#include "vmime/message.hpp" + + +namespace vmime { +namespace net { + + +class messageStructure; + + +/** A MIME part in a message. + */ + +class VMIME_EXPORT messagePart : public object +{ +protected: + + messagePart() { } + messagePart(const messagePart&) : object() { } + + virtual ~messagePart() { } + +public: + + /** Return the structure of this part. + * + * @return structure of the part + */ + virtual shared_ptr <const messageStructure> getStructure() const = 0; + + /** Return the structure of this part. + * + * @return structure of the part + */ + virtual shared_ptr <messageStructure> getStructure() = 0; + + /** Return the header section for this part (you must fetch header + * before using this function: see message::fetchPartHeader). + * + * @return header section + */ + virtual shared_ptr <const header> getHeader() const = 0; + + /** Return the media-type of the content in this part. + * + * @return content media type + */ + virtual const mediaType& getType() const = 0; + + /** Return the size of this part. + * + * @return size of the part (in bytes) + */ + virtual size_t getSize() const = 0; + + /** Return the part sequence number (index). + * The first part is at index zero. + * + * @return part number + */ + virtual int getNumber() const = 0; + + /** Return the sub-part at the specified position (zero is the + * first part). + * + * @param pos index of the sub-part + * @return sub-part at position 'pos' + */ + shared_ptr <const messagePart> getPartAt(const size_t pos) const; + + /** Return the sub-part at the specified position (zero is the + * first part). + * + * @param pos index of the sub-part + * @return sub-part at position 'pos' + */ + shared_ptr <messagePart> getPartAt(const size_t pos); + + /** Return the number of sub-parts in this part. + * + * @return number of sub-parts + */ + size_t getPartCount() const; +}; + + +/** Structure of a MIME part/message. + */ + +class VMIME_EXPORT messageStructure : public object +{ +protected: + + messageStructure() { } + messageStructure(const messageStructure&) : object() { } + +public: + + virtual ~messageStructure() { } + + /** Return the part at the specified position (first + * part is at position 0). + * + * @param pos position + * @return part at position 'pos' + */ + virtual shared_ptr <const messagePart> getPartAt(const size_t pos) const = 0; + + /** Return the part at the specified position (first + * part is at position 0). + * + * @param pos position + * @return part at position 'pos' + */ + virtual shared_ptr <messagePart> getPartAt(const size_t pos) = 0; + + /** Return the number of parts in this part. + * + * @return number of parts + */ + virtual size_t getPartCount() const = 0; +}; + + +/** Abstract representation of a message in a store/transport service. + */ + +class VMIME_EXPORT message : public object +{ +protected: + + message() { } + message(const message&) : object() { } + +public: + + virtual ~message() { } + + /** The type for an unique message identifier. + */ + class VMIME_EXPORT uid + { + public: + + uid(); + uid(const string& uid); + uid(const unsigned long uid); + uid(const char* uid); + uid(const uid& other); + + uid& operator=(const uid& other); + uid& operator=(const string& uid); + uid& operator=(const unsigned long uid); + + operator string() const; + + bool empty() const; + + bool operator==(const uid& other) const; + + private: + + string m_str; + }; + + /** Return the MIME structure of the message (must fetch before). + * + * @return MIME structure of the message + */ + virtual shared_ptr <const messageStructure> getStructure() const = 0; + + /** Return the MIME structure of the message (must fetch before). + * + * @return MIME structure of the message + */ + virtual shared_ptr <messageStructure> getStructure() = 0; + + /** Return a reference to the header fields of the message (must fetch before). + * + * @return header section of the message + */ + virtual shared_ptr <const header> getHeader() const = 0; + + /** Return the sequence number of this message. This number is + * used to reference the message in the folder. + * + * @return sequence number of the message + */ + virtual int getNumber() const = 0; + + /** Return the unique identifier (UID) of this message in its + * folder (must fetch before). + * + * @return UID of the message + */ + virtual const uid getUID() const = 0; + + /** Return the size of the message (must fetch before). + * + * @return size of the message (in bytes) + */ + virtual size_t getSize() const = 0; + + /** Check whether this message has been expunged (ie: definitively + * deleted) and does not exist in the folder anymore. + * + * @return true if the message is expunged, false otherwise + */ + virtual bool isExpunged() const = 0; + + /** Possible flags for a message. + */ + enum Flags + { + FLAG_SEEN = (1 << 0), /**< Message has been seen. */ + FLAG_RECENT = (1 << 1), /**< Message has been recently received. */ + FLAG_DELETED = (1 << 2), /**< Message is marked for deletion. */ + FLAG_REPLIED = (1 << 3), /**< User replied to this message. */ + FLAG_MARKED = (1 << 4), /**< Used-defined flag. */ + FLAG_PASSED = (1 << 5), /**< Message has been resent/forwarded/bounced. */ + FLAG_DRAFT = (1 << 6), /**< Message is marked as a 'draft'. */ + + FLAG_UNDEFINED = 9999 /**< Used internally (this should not be returned + by the flags() function). */ + }; + + /** Methods for setting the flags. + */ + enum FlagsModes + { + FLAG_MODE_SET, /**< Set (replace) the flags. */ + FLAG_MODE_ADD, /**< Add the flags. */ + FLAG_MODE_REMOVE /**< Remove the flags. */ + }; + + /** Return the flags of this message. + * + * @return flags of the message + */ + virtual int getFlags() const = 0; + + /** Set the flags of this message. + * + * @param flags set of flags (see Flags) + * @param mode indicate how to treat old and new flags (see FlagsModes) + */ + virtual void setFlags(const int flags, const int mode = FLAG_MODE_SET) = 0; + + /** Extract the whole message data (header + contents). + * + * \warning Partial fetch might not be supported by the underlying protocol. + * + * @param os output stream in which to write message data + * @param progress progress listener, or NULL if not used + * @param start index of the first byte to retrieve (used for partial fetch) + * @param length number of bytes to retrieve (used for partial fetch) + * @param peek if true, try not to mark the message as read. This may not + * be supported by the protocol (IMAP supports this), but it will NOT throw + * an exception if not supported. + */ + virtual void extract + (utility::outputStream& os, + utility::progressListener* progress = NULL, + const size_t start = 0, + const size_t length = -1, + const bool peek = false) const = 0; + + /** Extract the specified MIME part of the message (header + contents). + * + * \warning Partial fetch might not be supported by the underlying protocol. + * + * @param p part to extract + * @param os output stream in which to write part data + * @param progress progress listener, or NULL if not used + * @param start index of the first byte to retrieve (used for partial fetch) + * @param length number of bytes to retrieve (used for partial fetch) + * @param peek if true, try not to mark the message as read. This may not + * be supported by the protocol (IMAP supports this), but it will NOT throw + * an exception if not supported. + */ + virtual void extractPart + (shared_ptr <const messagePart> p, + utility::outputStream& os, + utility::progressListener* progress = NULL, + const size_t start = 0, + const size_t length = -1, + const bool peek = false) const = 0; + + /** Fetch the MIME header for the specified part. + * + * @param p the part for which to fetch the header + */ + virtual void fetchPartHeader(shared_ptr <messagePart> p) = 0; + + /** Get the RFC-822 message for this abstract message. + * Warning: This may require getting some data (ie: structure and headers) from + * the server, which is done automatically. Actual message contents (ie: body) + * will not be fetched if possible (IMAP allows it, whereas POP3 will require + * to fetch the whole message). + * + * @return a RFC-822-parsed message + */ + virtual shared_ptr <vmime::message> getParsedMessage() = 0; +}; + + +VMIME_EXPORT std::ostream& operator<<(std::ostream& os, const message::uid& uid); + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + +#endif // VMIME_NET_MESSAGE_HPP_INCLUDED diff --git a/src/vmime/net/messageSet.cpp b/src/vmime/net/messageSet.cpp new file mode 100644 index 00000000..2939042e --- /dev/null +++ b/src/vmime/net/messageSet.cpp @@ -0,0 +1,369 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/net/messageSet.hpp" + +#include <iterator> +#include <algorithm> +#include <typeinfo> + + +namespace vmime { +namespace net { + + +// messageRange + +messageRange::messageRange() +{ +} + + +messageRange::~messageRange() +{ +} + + +// numberMessageRange + +numberMessageRange::numberMessageRange(const int number) + : m_first(number), m_last(number) +{ + if (number < 1) + throw std::invalid_argument("number"); +} + + +numberMessageRange::numberMessageRange(const int first, const int last) + : m_first(first), m_last(last) +{ + if (first < 1) + throw std::invalid_argument("first"); + else if (last != -1 && last < first) + throw std::invalid_argument("last"); +} + + +numberMessageRange::numberMessageRange(const numberMessageRange& other) + : messageRange(), m_first(other.m_first), m_last(other.m_last) +{ +} + + +int numberMessageRange::getFirst() const +{ + return m_first; +} + + +int numberMessageRange::getLast() const +{ + return m_last; +} + + +void numberMessageRange::enumerate(messageSetEnumerator& en) const +{ + en.enumerateNumberMessageRange(*this); +} + + +messageRange* numberMessageRange::clone() const +{ + return new numberMessageRange(*this); +} + + +// UIDMessageRange + +UIDMessageRange::UIDMessageRange(const message::uid& uid) + : m_first(uid), m_last(uid) +{ +} + + +UIDMessageRange::UIDMessageRange(const message::uid& first, const message::uid& last) + : m_first(first), m_last(last) +{ +} + + +UIDMessageRange::UIDMessageRange(const UIDMessageRange& other) + : messageRange(), m_first(other.m_first), m_last(other.m_last) +{ +} + + +const message::uid UIDMessageRange::getFirst() const +{ + return m_first; +} + + +const message::uid UIDMessageRange::getLast() const +{ + return m_last; +} + + +void UIDMessageRange::enumerate(messageSetEnumerator& en) const +{ + en.enumerateUIDMessageRange(*this); +} + + +messageRange* UIDMessageRange::clone() const +{ + return new UIDMessageRange(*this); +} + + +// messageSet + + +messageSet::messageSet() +{ +} + + +messageSet::messageSet(const messageSet& other) + : object() +{ + m_ranges.resize(other.m_ranges.size()); + + for (size_t i = 0, n = other.m_ranges.size() ; i < n ; ++i) + m_ranges[i] = other.m_ranges[i]->clone(); +} + + +messageSet::~messageSet() +{ + for (size_t i = 0, n = m_ranges.size() ; i < n ; ++i) + delete m_ranges[i]; +} + + +// static +messageSet messageSet::byNumber(const int number) +{ + messageSet set; + set.m_ranges.push_back(new numberMessageRange(number)); + + return set; +} + + +// static +messageSet messageSet::byNumber(const int first, const int last) +{ + messageSet set; + set.m_ranges.push_back(new numberMessageRange(first, last)); + + return set; +} + + +// static +messageSet messageSet::byNumber(const std::vector <int>& numbers) +{ + // Sort a copy of the list + std::vector <int> sortedNumbers; + + sortedNumbers.resize(numbers.size()); + + std::copy(numbers.begin(), numbers.end(), sortedNumbers.begin()); + std::sort(sortedNumbers.begin(), sortedNumbers.end()); + + // Build the set by detecting ranges of continuous numbers + int previous = -1, rangeStart = -1; + messageSet set; + + for (std::vector <int>::const_iterator it = sortedNumbers.begin() ; + it != sortedNumbers.end() ; ++it) + { + const int current = *it; + + if (current == previous) + continue; // skip duplicates + + if (previous == -1) + { + previous = current; + rangeStart = current; + } + else + { + if (current == previous + 1) + { + previous = current; + } + else + { + set.m_ranges.push_back(new numberMessageRange(rangeStart, previous)); + + previous = current; + rangeStart = current; + } + } + } + + set.m_ranges.push_back(new numberMessageRange(rangeStart, previous)); + + return set; +} + + +// static +messageSet messageSet::byUID(const message::uid& uid) +{ + messageSet set; + set.m_ranges.push_back(new UIDMessageRange(uid)); + + return set; +} + + +messageSet messageSet::byUID(const message::uid& first, const message::uid& last) +{ + messageSet set; + set.m_ranges.push_back(new UIDMessageRange(first, last)); + + return set; +} + + +messageSet messageSet::byUID(const std::vector <message::uid>& uids) +{ + std::vector <vmime_uint32> numericUIDs; + + for (size_t i = 0, n = uids.size() ; i < n ; ++i) + { + const string uid = uids[i]; + int numericUID = 0; + + const char* p = uid.c_str(); + + for ( ; *p >= '0' && *p <= '9' ; ++p) + numericUID = (numericUID * 10) + (*p - '0'); + + if (*p != '\0') + { + messageSet set; + + // Non-numeric UID, fall back to plain UID list (single-UID ranges) + for (size_t i = 0, n = uids.size() ; i < n ; ++i) + set.m_ranges.push_back(new UIDMessageRange(uids[i])); + + return set; + } + + numericUIDs.push_back(numericUID); + } + + // Sort a copy of the list + std::vector <vmime_uint32> sortedUIDs; + + sortedUIDs.resize(numericUIDs.size()); + + std::copy(numericUIDs.begin(), numericUIDs.end(), sortedUIDs.begin()); + std::sort(sortedUIDs.begin(), sortedUIDs.end()); + + // Build the set by detecting ranges of continuous numbers + vmime_uint32 previous = -1U, rangeStart = -1U; + messageSet set; + + for (std::vector <vmime_uint32>::const_iterator it = sortedUIDs.begin() ; + it != sortedUIDs.end() ; ++it) + { + const vmime_uint32 current = *it; + + if (current == previous) + continue; // skip duplicates + + if (previous == -1U) + { + previous = current; + rangeStart = current; + } + else + { + if (current == previous + 1) + { + previous = current; + } + else + { + set.m_ranges.push_back(new UIDMessageRange + (utility::stringUtils::toString(rangeStart), + utility::stringUtils::toString(previous))); + + previous = current; + rangeStart = current; + } + } + } + + set.m_ranges.push_back(new UIDMessageRange + (utility::stringUtils::toString(rangeStart), + utility::stringUtils::toString(previous))); + + return set; +} + + +void messageSet::addRange(const messageRange& range) +{ + if (!m_ranges.empty() && typeid(*m_ranges[0]) != typeid(range)) + throw std::invalid_argument("range"); + + m_ranges.push_back(range.clone()); +} + + +void messageSet::enumerate(messageSetEnumerator& en) const +{ + for (size_t i = 0, n = m_ranges.size() ; i < n ; ++i) + m_ranges[i]->enumerate(en); +} + + +bool messageSet::isEmpty() const +{ + return m_ranges.empty(); +} + + +bool messageSet::isNumberSet() const +{ + return !isEmpty() && dynamic_cast <numberMessageRange*>(m_ranges[0]) != NULL; +} + + +bool messageSet::isUIDSet() const +{ + return !isEmpty() && dynamic_cast <UIDMessageRange*>(m_ranges[0]) != NULL; +} + + +} // net +} // vmime diff --git a/src/vmime/net/messageSet.hpp b/src/vmime/net/messageSet.hpp new file mode 100644 index 00000000..6c7d7f44 --- /dev/null +++ b/src/vmime/net/messageSet.hpp @@ -0,0 +1,335 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_MESSAGESET_HPP_INCLUDED +#define VMIME_NET_MESSAGESET_HPP_INCLUDED + + +#include "vmime/net/message.hpp" + + +namespace vmime { +namespace net { + + +// Forward references +class numberMessageRange; +class UIDMessageRange; + + +/** Enumerator used to retrieve the message number/UID ranges contained + * in a messageSet object. + */ + +class VMIME_EXPORT messageSetEnumerator +{ +public: + + virtual void enumerateNumberMessageRange(const numberMessageRange& range) = 0; + virtual void enumerateUIDMessageRange(const UIDMessageRange& range) = 0; +}; + + +/** A range of (continuous) messages, designated either by their + * sequence number, or by their UID. + */ + +class VMIME_EXPORT messageRange : public object +{ +public: + + virtual ~messageRange(); + + /** Enumerates this range with the specified enumerator. + * + * @param en enumerator that will receive the method calls while + * enumerating this range + */ + virtual void enumerate(messageSetEnumerator& en) const = 0; + + /** Clones this message range. + */ + virtual messageRange* clone() const = 0; + +protected: + + messageRange(); + messageRange(const messageRange&); +}; + + +/** A range of (continuous) messages designated by their sequence number. + */ + +class VMIME_EXPORT numberMessageRange : public messageRange +{ +public: + + /** Constructs a message range containing a single message. + * + * @param number message number (numbering starts at 1, not 0) + */ + numberMessageRange(const int number); + + /** Constructs a message range for multiple messages. + * + * @param first number of the first message in the range (numbering + * starts at 1, not 0) + * @param last number of the last message in the range, or use the + * special value -1 to designate the last message in the folder + */ + numberMessageRange(const int first, const int last); + + /** Constructs a message range by copying from another range. + * + * @param other range to copy + */ + numberMessageRange(const numberMessageRange& other); + + /** Returns the number of the first message in the range. + * + * @return number of the first message + */ + int getFirst() const; + + /** Returns the number of the last message in the range, or -1 + * to designate the last message in the folder + * + * @return number of the last message + */ + int getLast() const; + + void enumerate(messageSetEnumerator& en) const; + + messageRange* clone() const; + +private: + + int m_first, m_last; +}; + + +/** A range of (continuous) messages represented by their UID. + */ + +class VMIME_EXPORT UIDMessageRange : public messageRange +{ +public: + + /** Constructs a message range containing a single message. + * + * @param uid message UID + */ + UIDMessageRange(const message::uid& uid); + + /** Constructs a message range for multiple messages. + * + * @param first UID of the first message in the range + * @param last UID of the last message in the range, or use the + * special value '*' to designate the last message in the folder + */ + UIDMessageRange(const message::uid& first, const message::uid& last); + + /** Constructs a message range by copying from another range. + * + * @param other range to copy + */ + UIDMessageRange(const UIDMessageRange& other); + + /** Returns the UID of the first message in the range. + * + * @return UID of the first message + */ + const message::uid getFirst() const; + + /** Returns the UID of the last message in the range, or '*' + * to designate the last message in the folder + * + * @return UID of the last message + */ + const message::uid getLast() const; + + void enumerate(messageSetEnumerator& en) const; + + messageRange* clone() const; + +private: + + message::uid m_first, m_last; +}; + + +/** Represents a set of messages, designated either by their sequence + * number, or by their UID (but not both). + * + * Following is example code to designate messages by their number: + * \code{.cpp} + * // Designate a single message with sequence number 42 + * vmime::net::messageSet::byNumber(42) + * + * // Designate messages from sequence number 5 to sequence number 8 (including) + * vmime::net::messageSet::byNumber(5, 8) + * + * // Designate all messages in the folder, starting from number 42 + * vmime::net::messageSet::byNumber(42, -1) + * \endcode + * Or, to designate messages by their UID, use: + * \code{.cpp} + * // Designate a single message with UID 1042 + * vmime::net::messageSet::byUID(1042) + * + * // Designate messages from UID 1000 to UID 1042 (including) + * vmime::net::messageSet::byUID(1000, 1042) + * + * // Designate all messages in the folder, starting from UID 1000 + * vmime::net::messageSet::byUID(1000, "*") + * \endcode + */ + +class VMIME_EXPORT messageSet : public object +{ +public: + + ~messageSet(); + + messageSet(const messageSet& other); + + /** Constructs a new message set and initializes it with a single + * message represented by its sequence number. + * + * @param number message number (numbering starts at 1, not 0) + * @return new message set + */ + static messageSet byNumber(const int number); + + /** Constructs a new message set and initializes it with a range + * of messages represented by their sequence number. + * + * @param first number of the first message in the range (numbering + * starts at 1, not 0) + * @param last number of the last message in the range, or use the + * special value -1 to designate the last message in the folder + * @return new message set + */ + static messageSet byNumber(const int first, const int last); + + /** Constructs a new message set and initializes it with a possibly + * unsorted list of messages represented by their sequence number. + * Please note that numbering starts at 1, not 0. + * + * The function tries to group consecutive message numbers into + * ranges to reduce the size of the resulting set. + * + * For example, given the list "1,2,3,4,5,7,8,13,15,16,17" it will + * result in the following ranges: "1:5,7:8,13,15:17". + * + * @param numbers a vector containing numbers of the messages + * @return new message set + */ + static messageSet byNumber(const std::vector <int>& numbers); + + /** Constructs a new message set and initializes it with a single + * message represented by its UID. + * + * @param uid message UID + * @return new message set + */ + static messageSet byUID(const message::uid& uid); + + /** Constructs a new message set and initializes it with a range + * of messages represented by their sequence number. + * + * @param first UID of the first message in the range + * @param last UID of the last message in the range, or use the + * special value '*' to designate the last message in the folder + * @return new message set + */ + static messageSet byUID(const message::uid& first, const message::uid& last); + + /** Constructs a new message set and initializes it with a possibly + * unsorted list of messages represented by their UID. + * + * For UIDs that actually are numbers (this is the case for IMAP), the + * function tries to group consecutive UIDs into ranges to reduce the + * size of the resulting set. + * + * For example, given the list "1,2,3,4,5,7,8,13,15,16,17" it will + * result in the following ranges: "1:5,7:8,13,15:17". + * + * @param uids a vector containing UIDs of the messages + * @return new message set + */ + static messageSet byUID(const std::vector <message::uid>& uids); + + /** Adds the specified range to this set. The type of message range + * (either number or UID) must match the type of the ranges already + * contained in this set (ie. it's not possible to have a message + * set which contains both number ranges and UID ranges). + * + * @param range range to add + * @throw std::invalid_argument exception if the range type does + * not match the type of the ranges in this set + */ + void addRange(const messageRange& range); + + /** Enumerates this set with the specified enumerator. + * + * @param en enumerator that will receive the method calls while + * enumerating the ranges in this set + */ + void enumerate(messageSetEnumerator& en) const; + + /** Returns whether this set is empty (contains no range). + * + * @return true if this set is empty, or false otherwise + */ + bool isEmpty() const; + + /** Returns whether this set references messages by their sequence + * number. + * + * @return true if this set references messages by their sequence + * number, or false otherwise + */ + bool isNumberSet() const; + + /** Returns whether this set references messages by their UID. + * + * @return true if this set references messages by their UID, + * or false otherwise + */ + bool isUIDSet() const; + +private: + + messageSet(); + + std::vector <messageRange*> m_ranges; +}; + + +} // net +} // vmime + + +#endif // VMIME_NET_MESSAGESET_HPP_INCLUDED diff --git a/src/vmime/net/pop3/POP3Command.cpp b/src/vmime/net/pop3/POP3Command.cpp new file mode 100644 index 00000000..6fe301ce --- /dev/null +++ b/src/vmime/net/pop3/POP3Command.cpp @@ -0,0 +1,230 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + + +#include "vmime/net/pop3/POP3Command.hpp" +#include "vmime/net/pop3/POP3Connection.hpp" +#include "vmime/net/pop3/POP3Store.hpp" + +#include "vmime/net/socket.hpp" + +#include "vmime/mailbox.hpp" +#include "vmime/utility/outputStreamAdapter.hpp" + + +namespace vmime { +namespace net { +namespace pop3 { + + +POP3Command::POP3Command(const string& text) + : m_text(text) +{ +} + + +// static +shared_ptr <POP3Command> POP3Command::CAPA() +{ + return createCommand("CAPA"); +} + + +// static +shared_ptr <POP3Command> POP3Command::NOOP() +{ + return createCommand("NOOP"); +} + + +// static +shared_ptr <POP3Command> POP3Command::AUTH(const string& mechName) +{ + std::ostringstream cmd; + cmd.imbue(std::locale::classic()); + cmd << "AUTH " << mechName; + + return createCommand(cmd.str()); +} + + +// static +shared_ptr <POP3Command> POP3Command::STLS() +{ + return createCommand("STLS"); +} + + +// static +shared_ptr <POP3Command> POP3Command::APOP(const string& username, const string& digest) +{ + std::ostringstream cmd; + cmd.imbue(std::locale::classic()); + cmd << "APOP " << username << " " << digest; + + return createCommand(cmd.str()); +} + + +// static +shared_ptr <POP3Command> POP3Command::USER(const string& username) +{ + std::ostringstream cmd; + cmd.imbue(std::locale::classic()); + cmd << "USER " << username; + + return createCommand(cmd.str()); +} + + +// static +shared_ptr <POP3Command> POP3Command::PASS(const string& password) +{ + std::ostringstream cmd; + cmd.imbue(std::locale::classic()); + cmd << "PASS " << password; + + return createCommand(cmd.str()); +} + + +// static +shared_ptr <POP3Command> POP3Command::STAT() +{ + return createCommand("STAT"); +} + + +// static +shared_ptr <POP3Command> POP3Command::LIST() +{ + return createCommand("LIST"); +} + + +// static +shared_ptr <POP3Command> POP3Command::LIST(const unsigned long msg) +{ + std::ostringstream cmd; + cmd.imbue(std::locale::classic()); + cmd << "LIST " << msg; + + return createCommand(cmd.str()); +} + + +// static +shared_ptr <POP3Command> POP3Command::UIDL() +{ + return createCommand("UIDL"); +} + + +// static +shared_ptr <POP3Command> POP3Command::UIDL(const unsigned long msg) +{ + std::ostringstream cmd; + cmd.imbue(std::locale::classic()); + cmd << "UIDL " << msg; + + return createCommand(cmd.str()); +} + + +// static +shared_ptr <POP3Command> POP3Command::DELE(const unsigned long msg) +{ + std::ostringstream cmd; + cmd.imbue(std::locale::classic()); + cmd << "DELE " << msg; + + return createCommand(cmd.str()); +} + + +// static +shared_ptr <POP3Command> POP3Command::RETR(const unsigned long msg) +{ + std::ostringstream cmd; + cmd.imbue(std::locale::classic()); + cmd << "RETR " << msg; + + return createCommand(cmd.str()); +} + + +// static +shared_ptr <POP3Command> POP3Command::TOP(const unsigned long msg, const unsigned long lines) +{ + std::ostringstream cmd; + cmd.imbue(std::locale::classic()); + cmd << "TOP " << msg << " " << lines; + + return createCommand(cmd.str()); +} + + +// static +shared_ptr <POP3Command> POP3Command::RSET() +{ + return createCommand("RSET"); +} + + +// static +shared_ptr <POP3Command> POP3Command::QUIT() +{ + return createCommand("QUIT"); +} + + +// static +shared_ptr <POP3Command> POP3Command::createCommand(const string& text) +{ + return shared_ptr <POP3Command>(new POP3Command(text)); +} + + +const string POP3Command::getText() const +{ + return m_text; +} + + +void POP3Command::send(shared_ptr <POP3Connection> conn) +{ + conn->getSocket()->send(m_text + "\r\n"); +} + + +} // pop3 +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 diff --git a/src/vmime/net/pop3/POP3Command.hpp b/src/vmime/net/pop3/POP3Command.hpp new file mode 100644 index 00000000..cc3c4fd5 --- /dev/null +++ b/src/vmime/net/pop3/POP3Command.hpp @@ -0,0 +1,113 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_POP3_POP3COMMAND_HPP_INCLUDED +#define VMIME_NET_POP3_POP3COMMAND_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + + +#include "vmime/object.hpp" +#include "vmime/base.hpp" + + +namespace vmime { + + +class mailbox; + + +namespace net { +namespace pop3 { + + +class POP3Connection; + + +/** A POP3 command that will be sent to the server. + */ +class VMIME_EXPORT POP3Command : public object +{ +public: + + static shared_ptr <POP3Command> CAPA(); + static shared_ptr <POP3Command> NOOP(); + static shared_ptr <POP3Command> AUTH(const string& mechName); + static shared_ptr <POP3Command> STLS(); + static shared_ptr <POP3Command> APOP(const string& username, const string& digest); + static shared_ptr <POP3Command> USER(const string& username); + static shared_ptr <POP3Command> PASS(const string& password); + static shared_ptr <POP3Command> STAT(); + static shared_ptr <POP3Command> LIST(); + static shared_ptr <POP3Command> LIST(const unsigned long msg); + static shared_ptr <POP3Command> UIDL(); + static shared_ptr <POP3Command> UIDL(const unsigned long msg); + static shared_ptr <POP3Command> DELE(const unsigned long msg); + static shared_ptr <POP3Command> RETR(const unsigned long msg); + static shared_ptr <POP3Command> TOP(const unsigned long msg, const unsigned long lines); + static shared_ptr <POP3Command> RSET(); + static shared_ptr <POP3Command> QUIT(); + + /** Creates a new POP3 command with the specified text. + * + * @param text command text + * @return a new POP3Command object + */ + static shared_ptr <POP3Command> createCommand(const string& text); + + /** Sends this command over the specified connection. + * + * @param conn connection onto which the command will be sent + */ + virtual void send(shared_ptr <POP3Connection> conn); + + /** Returns the full text of the command, including command name + * and parameters (if any). + * + * @return command text (eg. "LIST 42") + */ + virtual const string getText() const; + +protected: + + POP3Command(const string& text); + POP3Command(const POP3Command&); + +private: + + string m_text; +}; + + +} // pop3 +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + +#endif // VMIME_NET_POP3_POP3COMMAND_HPP_INCLUDED diff --git a/src/vmime/net/pop3/POP3Connection.cpp b/src/vmime/net/pop3/POP3Connection.cpp new file mode 100644 index 00000000..5fa923f4 --- /dev/null +++ b/src/vmime/net/pop3/POP3Connection.cpp @@ -0,0 +1,675 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + + +#include "vmime/net/pop3/POP3Connection.hpp" +#include "vmime/net/pop3/POP3Store.hpp" + +#include "vmime/exception.hpp" +#include "vmime/platform.hpp" + +#include "vmime/security/digest/messageDigestFactory.hpp" + +#include "vmime/net/defaultConnectionInfos.hpp" + +#if VMIME_HAVE_SASL_SUPPORT + #include "vmime/security/sasl/SASLContext.hpp" +#endif // VMIME_HAVE_SASL_SUPPORT + +#if VMIME_HAVE_TLS_SUPPORT + #include "vmime/net/tls/TLSSession.hpp" + #include "vmime/net/tls/TLSSecuredConnectionInfos.hpp" +#endif // VMIME_HAVE_TLS_SUPPORT + + + +// Helpers for service properties +#define GET_PROPERTY(type, prop) \ + (m_store.lock()->getInfos().getPropertyValue <type>(getSession(), \ + dynamic_cast <const POP3ServiceInfos&>(m_store.lock()->getInfos()).getProperties().prop)) +#define HAS_PROPERTY(prop) \ + (m_store.lock()->getInfos().hasProperty(getSession(), \ + dynamic_cast <const POP3ServiceInfos&>(m_store.lock()->getInfos()).getProperties().prop)) + + +namespace vmime { +namespace net { +namespace pop3 { + + + +POP3Connection::POP3Connection(shared_ptr <POP3Store> store, shared_ptr <security::authenticator> auth) + : m_store(store), m_auth(auth), m_socket(null), m_timeoutHandler(null), + m_authenticated(false), m_secured(false), m_capabilitiesFetched(false) +{ +} + + +POP3Connection::~POP3Connection() +{ + try + { + if (isConnected()) + disconnect(); + else if (m_socket) + internalDisconnect(); + } + catch (vmime::exception&) + { + // Ignore + } +} + + +void POP3Connection::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); + + shared_ptr <POP3Store> store = m_store.lock(); + + // Create the time-out handler + if (store->getTimeoutHandlerFactory()) + m_timeoutHandler = store->getTimeoutHandlerFactory()->create(); + + // Create and connect the socket + m_socket = store->getSocketFactory()->create(m_timeoutHandler); + +#if VMIME_HAVE_TLS_SUPPORT + if (store->isPOP3S()) // dedicated port/POP3S + { + shared_ptr <tls::TLSSession> tlsSession = tls::TLSSession::create + (store->getCertificateVerifier(), + store->getSession()->getTLSProperties()); + + shared_ptr <tls::TLSSocket> tlsSocket = + tlsSession->getSocket(m_socket); + + m_socket = tlsSocket; + + m_secured = true; + m_cntInfos = make_shared <tls::TLSSecuredConnectionInfos>(address, port, tlsSession, tlsSocket); + } + else +#endif // VMIME_HAVE_TLS_SUPPORT + { + m_cntInfos = make_shared <defaultConnectionInfos>(address, port); + } + + m_socket->connect(address, port); + + // Connection + // + // eg: C: <connection to server> + // --- S: +OK MailSite POP3 Server 5.3.4.0 Ready <[email protected]> + + shared_ptr <POP3Response> response = POP3Response::readResponse + (dynamicCast <POP3Connection>(shared_from_this())); + + if (!response->isSuccess()) + { + internalDisconnect(); + throw exceptions::connection_greeting_error(response->getFirstLine()); + } + +#if VMIME_HAVE_TLS_SUPPORT + // Setup secured connection, if requested + const bool tls = HAS_PROPERTY(PROPERTY_CONNECTION_TLS) + && GET_PROPERTY(bool, PROPERTY_CONNECTION_TLS); + const bool tlsRequired = HAS_PROPERTY(PROPERTY_CONNECTION_TLS_REQUIRED) + && GET_PROPERTY(bool, PROPERTY_CONNECTION_TLS_REQUIRED); + + if (!store->isPOP3S() && tls) // only if not POP3S + { + try + { + startTLS(); + } + // Non-fatal error + catch (exceptions::command_error&) + { + if (tlsRequired) + { + throw; + } + else + { + // TLS is not required, so don't bother + } + } + // Fatal error + catch (...) + { + throw; + } + } +#endif // VMIME_HAVE_TLS_SUPPORT + + // Start authentication process + authenticate(messageId(response->getText())); +} + + +void POP3Connection::disconnect() +{ + if (!isConnected()) + throw exceptions::not_connected(); + + internalDisconnect(); +} + + +void POP3Connection::internalDisconnect() +{ + if (m_socket) + { + if (m_socket->isConnected()) + { + try + { + POP3Command::QUIT()->send(dynamicCast <POP3Connection>(shared_from_this())); + POP3Response::readResponse(dynamicCast <POP3Connection>(shared_from_this())); + } + catch (exception&) + { + // Not important + } + + m_socket->disconnect(); + } + + m_socket = null; + } + + m_timeoutHandler = null; + + m_authenticated = false; + m_secured = false; + + m_cntInfos = null; +} + + +void POP3Connection::authenticate(const messageId& randomMID) +{ + getAuthenticator()->setService(m_store.lock()); + +#if VMIME_HAVE_SASL_SUPPORT + // First, try SASL authentication + if (GET_PROPERTY(bool, PROPERTY_OPTIONS_SASL)) + { + try + { + authenticateSASL(); + + m_authenticated = true; + return; + } + catch (exceptions::authentication_error& e) + { + if (!GET_PROPERTY(bool, PROPERTY_OPTIONS_SASL_FALLBACK)) + { + // Can't fallback on APOP/normal authentication + internalDisconnect(); + throw e; + } + else + { + // Ignore, will try APOP/normal authentication + } + } + catch (exception& e) + { + internalDisconnect(); + throw e; + } + } +#endif // VMIME_HAVE_SASL_SUPPORT + + // Secured authentication with APOP (if requested and if available) + // + // eg: C: APOP vincent <digest> + // --- S: +OK vincent is a valid mailbox + + const string username = getAuthenticator()->getUsername(); + const string password = getAuthenticator()->getPassword(); + + shared_ptr <POP3Connection> conn = dynamicCast <POP3Connection>(shared_from_this()); + shared_ptr <POP3Response> response; + + if (GET_PROPERTY(bool, PROPERTY_OPTIONS_APOP)) + { + if (randomMID.getLeft().length() != 0 && + randomMID.getRight().length() != 0) + { + // <digest> is the result of MD5 applied to "<message-id>password" + shared_ptr <security::digest::messageDigest> md5 = + security::digest::messageDigestFactory::getInstance()->create("md5"); + + md5->update(randomMID.generate() + password); + md5->finalize(); + + POP3Command::APOP(username, md5->getHexDigest())->send(conn); + response = POP3Response::readResponse(conn); + + if (response->isSuccess()) + { + m_authenticated = true; + return; + } + else + { + // Some servers close the connection after an unsuccessful APOP + // command, so the fallback may not always work... + // + // S: +OK Qpopper (version 4.0.5) at xxx starting. <30396.1126730747@xxx> + // C: APOP plop c5e0a87d088ec71d60e32692d4c5bdf4 + // S: -ERR [AUTH] Password supplied for "plop" is incorrect. + // S: +OK Pop server at xxx signing off. + // [Connection closed by foreign host.] + + if (!GET_PROPERTY(bool, PROPERTY_OPTIONS_APOP_FALLBACK)) + { + // Can't fallback on basic authentication + internalDisconnect(); + throw exceptions::authentication_error(response->getFirstLine()); + } + + // Ensure connection is valid (cf. note above) + try + { + POP3Command::NOOP()->send(conn); + POP3Response::readResponse(conn); + } + catch (exceptions::socket_exception&) + { + internalDisconnect(); + throw exceptions::authentication_error(response->getFirstLine()); + } + } + } + else + { + // APOP not supported + if (!GET_PROPERTY(bool, PROPERTY_OPTIONS_APOP_FALLBACK)) + { + // Can't fallback on basic authentication + internalDisconnect(); + throw exceptions::authentication_error("APOP not supported"); + } + } + } + + // 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) + POP3Command::USER(username)->send(conn); + response = POP3Response::readResponse(conn); + + if (!response->isSuccess()) + { + internalDisconnect(); + throw exceptions::authentication_error(response->getFirstLine()); + } + + POP3Command::PASS(password)->send(conn); + response = POP3Response::readResponse(conn); + + if (!response->isSuccess()) + { + internalDisconnect(); + throw exceptions::authentication_error(response->getFirstLine()); + } + + m_authenticated = true; +} + + +#if VMIME_HAVE_SASL_SUPPORT + +void POP3Connection::authenticateSASL() +{ + if (!dynamicCast <security::sasl::SASLAuthenticator>(getAuthenticator())) + throw exceptions::authentication_error("No SASL authenticator available."); + + std::vector <string> capa = getCapabilities(); + std::vector <string> saslMechs; + + for (unsigned int i = 0 ; i < capa.size() ; ++i) + { + const string& x = capa[i]; + + // C: CAPA + // S: +OK List of capabilities follows + // S: LOGIN-DELAY 0 + // S: PIPELINING + // S: UIDL + // S: ... + // S: SASL DIGEST-MD5 CRAM-MD5 <----- + // S: EXPIRE NEVER + // S: ... + + if (x.length() > 5 && + (x[0] == 'S' || x[0] == 's') && + (x[1] == 'A' || x[1] == 'a') && + (x[2] == 'S' || x[2] == 's') && + (x[3] == 'L' || x[3] == 'l') && + (x[4] == ' ' || x[4] == '\t')) + { + const string list(x.begin() + 5, x.end()); + + std::istringstream iss(list); + string mech; + + while (iss >> mech) + saslMechs.push_back(mech); + } + } + + if (saslMechs.empty()) + throw exceptions::authentication_error("No SASL mechanism available."); + + std::vector <shared_ptr <security::sasl::SASLMechanism> > mechList; + + shared_ptr <security::sasl::SASLContext> saslContext = + make_shared <security::sasl::SASLContext>(); + + for (unsigned int i = 0 ; i < saslMechs.size() ; ++i) + { + try + { + mechList.push_back + (saslContext->createMechanism(saslMechs[i])); + } + catch (exceptions::no_such_mechanism&) + { + // Ignore mechanism + } + } + + if (mechList.empty()) + throw exceptions::authentication_error("No SASL mechanism available."); + + // Try to suggest a mechanism among all those supported + shared_ptr <security::sasl::SASLMechanism> suggestedMech = + saslContext->suggestMechanism(mechList); + + if (!suggestedMech) + throw exceptions::authentication_error("Unable to suggest SASL mechanism."); + + // Allow application to choose which mechanisms to use + mechList = dynamicCast <security::sasl::SASLAuthenticator>(getAuthenticator())-> + getAcceptableMechanisms(mechList, suggestedMech); + + if (mechList.empty()) + throw exceptions::authentication_error("No SASL mechanism available."); + + // Try each mechanism in the list in turn + for (unsigned int i = 0 ; i < mechList.size() ; ++i) + { + shared_ptr <security::sasl::SASLMechanism> mech = mechList[i]; + + shared_ptr <security::sasl::SASLSession> saslSession = + saslContext->createSession("pop3", getAuthenticator(), mech); + + saslSession->init(); + + POP3Command::AUTH(mech->getName())->send(dynamicCast <POP3Connection>(shared_from_this())); + + for (bool cont = true ; cont ; ) + { + shared_ptr <POP3Response> response = + POP3Response::readResponse(dynamicCast <POP3Connection>(shared_from_this())); + + switch (response->getCode()) + { + case POP3Response::CODE_OK: + { + m_socket = saslSession->getSecuredSocket(m_socket); + return; + } + case POP3Response::CODE_READY: + { + byte_t* challenge = 0; + size_t challengeLen = 0; + + byte_t* resp = 0; + size_t respLen = 0; + + try + { + // Extract challenge + saslContext->decodeB64(response->getText(), &challenge, &challengeLen); + + // Prepare response + saslSession->evaluateChallenge + (challenge, challengeLen, &resp, &respLen); + + // Send response + m_socket->send(saslContext->encodeB64(resp, respLen) + "\r\n"); + } + catch (exceptions::sasl_exception& e) + { + if (challenge) + { + delete [] challenge; + challenge = NULL; + } + + if (resp) + { + delete [] resp; + resp = NULL; + } + + // Cancel SASL exchange + m_socket->send("*\r\n"); + } + catch (...) + { + if (challenge) + delete [] challenge; + + if (resp) + delete [] resp; + + throw; + } + + if (challenge) + delete [] challenge; + + if (resp) + delete [] resp; + + break; + } + default: + + cont = false; + break; + } + } + } + + throw exceptions::authentication_error + ("Could not authenticate using SASL: all mechanisms failed."); +} + +#endif // VMIME_HAVE_SASL_SUPPORT + + +#if VMIME_HAVE_TLS_SUPPORT + +void POP3Connection::startTLS() +{ + try + { + POP3Command::STLS()->send(dynamicCast <POP3Connection>(shared_from_this())); + + shared_ptr <POP3Response> response = + POP3Response::readResponse(dynamicCast <POP3Connection>(shared_from_this())); + + if (!response->isSuccess()) + throw exceptions::command_error("STLS", response->getFirstLine()); + + shared_ptr <tls::TLSSession> tlsSession = tls::TLSSession::create + (m_store.lock()->getCertificateVerifier(), + m_store.lock()->getSession()->getTLSProperties()); + + shared_ptr <tls::TLSSocket> tlsSocket = + tlsSession->getSocket(m_socket); + + tlsSocket->handshake(m_timeoutHandler); + + m_socket = tlsSocket; + + m_secured = true; + m_cntInfos = make_shared <tls::TLSSecuredConnectionInfos> + (m_cntInfos->getHost(), m_cntInfos->getPort(), tlsSession, tlsSocket); + + // " Once TLS has been started, the client MUST discard cached + // information about server capabilities and SHOULD re-issue + // the CAPA command. This is necessary to protect against + // man-in-the-middle attacks which alter the capabilities list + // prior to STLS. " (RFC-2595) + invalidateCapabilities(); + } + catch (exceptions::command_error&) + { + // Non-fatal error + throw; + } + catch (exception&) + { + // Fatal error + internalDisconnect(); + throw; + } +} + +#endif // VMIME_HAVE_TLS_SUPPORT + + +const std::vector <string> POP3Connection::getCapabilities() +{ + if (!m_capabilitiesFetched) + fetchCapabilities(); + + return m_capabilities; +} + + +void POP3Connection::invalidateCapabilities() +{ + m_capabilities.clear(); + m_capabilitiesFetched = false; +} + + +void POP3Connection::fetchCapabilities() +{ + POP3Command::CAPA()->send(dynamicCast <POP3Connection>(shared_from_this())); + + shared_ptr <POP3Response> response = + POP3Response::readMultilineResponse(dynamicCast <POP3Connection>(shared_from_this())); + + std::vector <string> res; + + if (response->isSuccess()) + { + for (size_t i = 0, n = response->getLineCount() ; i < n ; ++i) + res.push_back(response->getLineAt(i)); + } + + m_capabilities = res; + m_capabilitiesFetched = true; +} + + +bool POP3Connection::isConnected() const +{ + return m_socket && m_socket->isConnected() && m_authenticated; +} + + +bool POP3Connection::isSecuredConnection() const +{ + return m_secured; +} + + +shared_ptr <connectionInfos> POP3Connection::getConnectionInfos() const +{ + return m_cntInfos; +} + + +shared_ptr <POP3Store> POP3Connection::getStore() +{ + return m_store.lock(); +} + + +shared_ptr <session> POP3Connection::getSession() +{ + return m_store.lock()->getSession(); +} + + +shared_ptr <socket> POP3Connection::getSocket() +{ + return m_socket; +} + + +shared_ptr <timeoutHandler> POP3Connection::getTimeoutHandler() +{ + return m_timeoutHandler; +} + + +shared_ptr <security::authenticator> POP3Connection::getAuthenticator() +{ + return m_auth; +} + + +} // pop3 +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 diff --git a/src/vmime/net/pop3/POP3Connection.hpp b/src/vmime/net/pop3/POP3Connection.hpp new file mode 100644 index 00000000..3622f745 --- /dev/null +++ b/src/vmime/net/pop3/POP3Connection.hpp @@ -0,0 +1,125 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_POP3_POP3CONNECTION_HPP_INCLUDED +#define VMIME_NET_POP3_POP3CONNECTION_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + + +#include "vmime/messageId.hpp" + +#include "vmime/net/socket.hpp" +#include "vmime/net/timeoutHandler.hpp" +#include "vmime/net/session.hpp" +#include "vmime/net/connectionInfos.hpp" + +#include "vmime/net/pop3/POP3Command.hpp" +#include "vmime/net/pop3/POP3Response.hpp" + +#include "vmime/security/authenticator.hpp" + + +namespace vmime { +namespace net { + + +class socket; +class timeoutHandler; + + +namespace pop3 { + + +class POP3Store; + + +/** Manage connection to a POP3 server. + */ +class VMIME_EXPORT POP3Connection : public object +{ +public: + + POP3Connection(shared_ptr <POP3Store> store, shared_ptr <security::authenticator> auth); + virtual ~POP3Connection(); + + + virtual void connect(); + virtual bool isConnected() const; + virtual void disconnect(); + + bool isSecuredConnection() const; + shared_ptr <connectionInfos> getConnectionInfos() const; + + virtual shared_ptr <POP3Store> getStore(); + virtual shared_ptr <socket> getSocket(); + virtual shared_ptr <timeoutHandler> getTimeoutHandler(); + virtual shared_ptr <security::authenticator> getAuthenticator(); + virtual shared_ptr <session> getSession(); + +private: + + void authenticate(const messageId& randomMID); +#if VMIME_HAVE_SASL_SUPPORT + void authenticateSASL(); +#endif // VMIME_HAVE_SASL_SUPPORT + +#if VMIME_HAVE_TLS_SUPPORT + void startTLS(); +#endif // VMIME_HAVE_TLS_SUPPORT + + void fetchCapabilities(); + void invalidateCapabilities(); + const std::vector <string> getCapabilities(); + + void internalDisconnect(); + + + weak_ptr <POP3Store> m_store; + + shared_ptr <security::authenticator> m_auth; + shared_ptr <socket> m_socket; + shared_ptr <timeoutHandler> m_timeoutHandler; + + bool m_authenticated; + bool m_secured; + + shared_ptr <connectionInfos> m_cntInfos; + + std::vector <string> m_capabilities; + bool m_capabilitiesFetched; +}; + + +} // pop3 +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + +#endif // VMIME_NET_POP3_POP3CONNECTION_HPP_INCLUDED diff --git a/src/vmime/net/pop3/POP3Folder.cpp b/src/vmime/net/pop3/POP3Folder.cpp new file mode 100644 index 00000000..096de8af --- /dev/null +++ b/src/vmime/net/pop3/POP3Folder.cpp @@ -0,0 +1,729 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + + +#include "vmime/net/pop3/POP3Folder.hpp" + +#include "vmime/net/pop3/POP3Store.hpp" +#include "vmime/net/pop3/POP3Message.hpp" +#include "vmime/net/pop3/POP3Command.hpp" +#include "vmime/net/pop3/POP3Response.hpp" +#include "vmime/net/pop3/POP3FolderStatus.hpp" + +#include "vmime/net/pop3/POP3Utils.hpp" + +#include "vmime/exception.hpp" + + +namespace vmime { +namespace net { +namespace pop3 { + + +POP3Folder::POP3Folder(const folder::path& path, shared_ptr <POP3Store> store) + : m_store(store), m_path(path), + m_name(path.isEmpty() ? folder::path::component("") : path.getLastComponent()), + m_mode(-1), m_open(false) +{ + store->registerFolder(this); +} + + +POP3Folder::~POP3Folder() +{ + shared_ptr <POP3Store> store = m_store.lock(); + + if (store) + { + if (m_open) + close(false); + + store->unregisterFolder(this); + } + else if (m_open) + { + onClose(); + } +} + + +int POP3Folder::getMode() const +{ + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + return (m_mode); +} + + +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(); +} + + +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) +{ + shared_ptr <POP3Store> store = m_store.lock(); + + if (!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") + { + POP3Command::STAT()->send(store->getConnection()); + + shared_ptr <POP3Response> response = POP3Response::readResponse(store->getConnection()); + + if (!response->isSuccess()) + throw exceptions::command_error("STAT", response->getFirstLine()); + + std::istringstream iss(response->getText()); + iss >> m_messageCount; + + if (iss.fail()) + throw exceptions::invalid_response("STAT", response->getFirstLine()); + + m_open = true; + m_mode = mode; + } + else + { + throw exceptions::folder_not_found(); + } +} + +void POP3Folder::close(const bool expunge) +{ + shared_ptr <POP3Store> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + + if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + if (!expunge) + { + POP3Command::RSET()->send(store->getConnection()); + POP3Response::readResponse(store->getConnection()); + } + + 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(); +} + + +void POP3Folder::destroy() +{ + throw exceptions::operation_not_supported(); +} + + +bool POP3Folder::exists() +{ + shared_ptr <POP3Store> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + + return (m_path.isEmpty() || (m_path.getSize() == 1 && m_path[0].getBuffer() == "INBOX")); +} + + +bool POP3Folder::isOpen() const +{ + return (m_open); +} + + +shared_ptr <message> POP3Folder::getMessage(const int num) +{ + shared_ptr <POP3Store> store = m_store.lock(); + + if (!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 make_shared <POP3Message>(dynamicCast <POP3Folder>(shared_from_this()), num); +} + + +std::vector <shared_ptr <message> > POP3Folder::getMessages(const messageSet& msgs) +{ + shared_ptr <POP3Store> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + if (msgs.isNumberSet()) + { + const std::vector <int> numbers = POP3Utils::messageSetToNumberList(msgs); + + std::vector <shared_ptr <message> > messages; + shared_ptr <POP3Folder> thisFolder(dynamicCast <POP3Folder>(shared_from_this())); + + for (std::vector <int>::const_iterator it = numbers.begin() ; it != numbers.end() ; ++it) + { + if (*it < 1|| *it > m_messageCount) + throw exceptions::message_not_found(); + + messages.push_back(make_shared <POP3Message>(thisFolder, *it)); + } + + return messages; + } + else + { + throw exceptions::operation_not_supported(); + } +} + + +int POP3Folder::getMessageCount() +{ + shared_ptr <POP3Store> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + return (m_messageCount); +} + + +shared_ptr <folder> POP3Folder::getFolder(const folder::path::component& name) +{ + shared_ptr <POP3Store> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + + return make_shared <POP3Folder>(m_path / name, store); +} + + +std::vector <shared_ptr <folder> > POP3Folder::getFolders(const bool /* recursive */) +{ + shared_ptr <POP3Store> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + + if (m_path.isEmpty()) + { + std::vector <shared_ptr <folder> > v; + v.push_back(make_shared <POP3Folder>(folder::path::component("INBOX"), store)); + return (v); + } + else + { + std::vector <shared_ptr <folder> > v; + return (v); + } +} + + +void POP3Folder::fetchMessages(std::vector <shared_ptr <message> >& msg, const fetchAttributes& options, + utility::progressListener* progress) +{ + shared_ptr <POP3Store> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + const size_t total = msg.size(); + size_t current = 0; + + if (progress) + progress->start(total); + + for (std::vector <shared_ptr <message> >::iterator it = msg.begin() ; + it != msg.end() ; ++it) + { + dynamicCast <POP3Message>(*it)->fetch + (dynamicCast <POP3Folder>(shared_from_this()), options); + + if (progress) + progress->progress(++current, total); + } + + if (options.has(fetchAttributes::SIZE)) + { + // Send the "LIST" command + POP3Command::LIST()->send(store->getConnection()); + + // Get the response + shared_ptr <POP3Response> response = + POP3Response::readMultilineResponse(store->getConnection()); + + if (response->isSuccess()) + { + // C: LIST + // S: +OK + // S: 1 47548 + // S: 2 12653 + // S: . + std::map <int, string> result; + POP3Utils::parseMultiListOrUidlResponse(response, result); + + for (std::vector <shared_ptr <message> >::iterator it = msg.begin() ; + it != msg.end() ; ++it) + { + shared_ptr <POP3Message> m = dynamicCast <POP3Message>(*it); + + std::map <int, string>::const_iterator x = result.find(m->m_num); + + if (x != result.end()) + { + size_t size = 0; + + std::istringstream iss((*x).second); + iss >> size; + + m->m_size = size; + } + } + } + + } + + if (options.has(fetchAttributes::UID)) + { + // Send the "UIDL" command + POP3Command::UIDL()->send(store->getConnection()); + + // Get the response + shared_ptr <POP3Response> response = + POP3Response::readMultilineResponse(store->getConnection()); + + if (response->isSuccess()) + { + // C: UIDL + // S: +OK + // S: 1 whqtswO00WBw418f9t5JxYwZ + // S: 2 QhdPYR:00WBw1Ph7x7 + // S: . + std::map <int, string> result; + POP3Utils::parseMultiListOrUidlResponse(response, result); + + for (std::vector <shared_ptr <message> >::iterator it = msg.begin() ; + it != msg.end() ; ++it) + { + shared_ptr <POP3Message> m = dynamicCast <POP3Message>(*it); + + 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(shared_ptr <message> msg, const fetchAttributes& options) +{ + shared_ptr <POP3Store> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + + dynamicCast <POP3Message>(msg)->fetch + (dynamicCast <POP3Folder>(shared_from_this()), options); + + if (options.has(fetchAttributes::SIZE)) + { + // Send the "LIST" command + POP3Command::LIST(msg->getNumber())->send(store->getConnection()); + + // Get the response + shared_ptr <POP3Response> response = + POP3Response::readResponse(store->getConnection()); + + if (response->isSuccess()) + { + string responseText = response->getText(); + + // C: LIST 2 + // S: +OK 2 4242 + string::iterator it = responseText.begin(); + + while (it != responseText.end() && (*it == ' ' || *it == '\t')) ++it; + while (it != responseText.end() && !(*it == ' ' || *it == '\t')) ++it; + while (it != responseText.end() && (*it == ' ' || *it == '\t')) ++it; + + if (it != responseText.end()) + { + size_t size = 0; + + std::istringstream iss(string(it, responseText.end())); + iss >> size; + + dynamicCast <POP3Message>(msg)->m_size = size; + } + } + } + + if (options.has(fetchAttributes::UID)) + { + // Send the "UIDL" command + POP3Command::UIDL(msg->getNumber())->send(store->getConnection()); + + // Get the response + shared_ptr <POP3Response> response = + POP3Response::readResponse(store->getConnection()); + + if (response->isSuccess()) + { + string responseText = response->getText(); + + // C: UIDL 2 + // S: +OK 2 QhdPYR:00WBw1Ph7x7 + string::iterator it = responseText.begin(); + + while (it != responseText.end() && (*it == ' ' || *it == '\t')) ++it; + while (it != responseText.end() && !(*it == ' ' || *it == '\t')) ++it; + while (it != responseText.end() && (*it == ' ' || *it == '\t')) ++it; + + if (it != responseText.end()) + { + dynamicCast <POP3Message>(msg)->m_uid = + string(it, responseText.end()); + } + } + } +} + + +int POP3Folder::getFetchCapabilities() const +{ + return fetchAttributes::ENVELOPE | fetchAttributes::CONTENT_INFO | + fetchAttributes::SIZE | fetchAttributes::FULL_HEADER | + fetchAttributes::UID | fetchAttributes::IMPORTANCE; +} + + +shared_ptr <folder> POP3Folder::getParent() +{ + if (m_path.isEmpty()) + return null; + else + return make_shared <POP3Folder>(m_path.getParent(), m_store.lock()); +} + + +shared_ptr <const store> POP3Folder::getStore() const +{ + return m_store.lock(); +} + + +shared_ptr <store> POP3Folder::getStore() +{ + return m_store.lock(); +} + + +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.reset(); +} + + +void POP3Folder::deleteMessages(const messageSet& msgs) +{ + shared_ptr <POP3Store> store = m_store.lock(); + + const std::vector <int> nums = POP3Utils::messageSetToNumberList(msgs); + + if (nums.empty()) + throw exceptions::invalid_argument(); + + if (!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) + { + POP3Command::DELE(*it)->send(store->getConnection()); + + shared_ptr <POP3Response> response = + POP3Response::readResponse(store->getConnection()); + + if (!response->isSuccess()) + throw exceptions::command_error("DELE", response->getFirstLine()); + } + + // 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 + shared_ptr <events::messageChangedEvent> event = + make_shared <events::messageChangedEvent> + (dynamicCast <folder>(shared_from_this()), + events::messageChangedEvent::TYPE_FLAGS, list); + + notifyMessageChanged(event); +} + + +void POP3Folder::setMessageFlags(const messageSet& /* msgs */, + 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 + (shared_ptr <vmime::message> /* msg */, const int /* flags */, + vmime::datetime* /* date */, utility::progressListener* /* progress */) +{ + throw exceptions::operation_not_supported(); +} + + +void POP3Folder::addMessage + (utility::inputStream& /* is */, const size_t /* size */, const int /* flags */, + vmime::datetime* /* date */, utility::progressListener* /* progress */) +{ + throw exceptions::operation_not_supported(); +} + + +void POP3Folder::copyMessages(const folder::path& /* dest */, const messageSet& /* msgs */) +{ + throw exceptions::operation_not_supported(); +} + + +void POP3Folder::status(int& count, int& unseen) +{ + count = 0; + unseen = 0; + + shared_ptr <folderStatus> status = getStatus(); + + count = status->getMessageCount(); + unseen = status->getUnseenCount(); +} + + +shared_ptr <folderStatus> POP3Folder::getStatus() +{ + shared_ptr <POP3Store> store = m_store.lock(); + + if (!store) + throw exceptions::illegal_state("Store disconnected"); + + POP3Command::STAT()->send(store->getConnection()); + + shared_ptr <POP3Response> response = + POP3Response::readResponse(store->getConnection()); + + if (!response->isSuccess()) + throw exceptions::command_error("STAT", response->getFirstLine()); + + + unsigned int count = 0; + + std::istringstream iss(response->getText()); + iss >> count; + + shared_ptr <POP3FolderStatus> status = make_shared <POP3FolderStatus>(); + + status->setMessageCount(count); + status->setUnseenCount(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 + shared_ptr <events::messageCountEvent> event = + make_shared <events::messageCountEvent> + (dynamicCast <folder>(shared_from_this()), + events::messageCountEvent::TYPE_ADDED, nums); + + notifyMessageCount(event); + + // Notify folders with the same path + for (std::list <POP3Folder*>::iterator it = store->m_folders.begin() ; + it != store->m_folders.end() ; ++it) + { + if ((*it) != this && (*it)->getFullPath() == m_path) + { + (*it)->m_messageCount = count; + + shared_ptr <events::messageCountEvent> event = + make_shared <events::messageCountEvent> + (dynamicCast <folder>((*it)->shared_from_this()), + events::messageCountEvent::TYPE_ADDED, nums); + + (*it)->notifyMessageCount(event); + } + } + } + } + + return status; +} + + +void POP3Folder::expunge() +{ + // Not supported by POP3 protocol (deleted messages are automatically + // expunged at the end of the session...). +} + + +std::vector <int> POP3Folder::getMessageNumbersStartingOnUID(const message::uid& /* uid */) +{ + throw exceptions::operation_not_supported(); +} + + +} // pop3 +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + diff --git a/src/vmime/net/pop3/POP3Folder.hpp b/src/vmime/net/pop3/POP3Folder.hpp new file mode 100644 index 00000000..27ea6e5f --- /dev/null +++ b/src/vmime/net/pop3/POP3Folder.hpp @@ -0,0 +1,157 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_POP3_POP3FOLDER_HPP_INCLUDED +#define VMIME_NET_POP3_POP3FOLDER_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + + +#include <vector> +#include <map> + +#include "vmime/types.hpp" + +#include "vmime/net/folder.hpp" + + +namespace vmime { +namespace net { +namespace pop3 { + + +class POP3Store; +class POP3Message; + + +/** POP3 folder implementation. + */ + +class VMIME_EXPORT POP3Folder : public folder +{ +private: + + friend class POP3Store; + friend class POP3Message; + + POP3Folder(const POP3Folder&); + +public: + + POP3Folder(const folder::path& path, shared_ptr <POP3Store> store); + + ~POP3Folder(); + + int getMode() const; + + int getType(); + + int getFlags(); + + const folder::path::component getName() const; + const folder::path getFullPath() const; + + void open(const int mode, bool failIfModeIsNotAvailable = false); + void close(const bool expunge); + void create(const int type); + + bool exists(); + + void destroy(); + + bool isOpen() const; + + shared_ptr <message> getMessage(const int num); + std::vector <shared_ptr <message> > getMessages(const messageSet& msgs); + + int getMessageCount(); + + shared_ptr <folder> getFolder(const folder::path::component& name); + std::vector <shared_ptr <folder> > getFolders(const bool recursive = false); + + void rename(const folder::path& newPath); + + void deleteMessages(const messageSet& msgs); + + void setMessageFlags(const messageSet& msgs, const int flags, const int mode = message::FLAG_MODE_SET); + + void addMessage(shared_ptr <vmime::message> msg, const int flags = message::FLAG_UNDEFINED, vmime::datetime* date = NULL, utility::progressListener* progress = NULL); + void addMessage(utility::inputStream& is, const size_t size, const int flags = message::FLAG_UNDEFINED, vmime::datetime* date = NULL, utility::progressListener* progress = NULL); + + void copyMessages(const folder::path& dest, const messageSet& msgs); + + void status(int& count, int& unseen); + shared_ptr <folderStatus> getStatus(); + + void expunge(); + + shared_ptr <folder> getParent(); + + shared_ptr <const store> getStore() const; + shared_ptr <store> getStore(); + + + void fetchMessages(std::vector <shared_ptr <message> >& msg, const fetchAttributes& options, utility::progressListener* progress = NULL); + void fetchMessage(shared_ptr <message> msg, const fetchAttributes& options); + + int getFetchCapabilities() const; + + std::vector <int> getMessageNumbersStartingOnUID(const message::uid& uid); + +private: + + void registerMessage(POP3Message* msg); + void unregisterMessage(POP3Message* msg); + + void onStoreDisconnected(); + + void onClose(); + + + weak_ptr <POP3Store> m_store; + + folder::path m_path; + folder::path::component m_name; + + int m_mode; + bool m_open; + + int m_messageCount; + + typedef std::map <POP3Message*, int> MessageMap; + MessageMap m_messages; +}; + + +} // pop3 +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + +#endif // VMIME_NET_POP3_POP3FOLDER_HPP_INCLUDED diff --git a/src/vmime/net/pop3/POP3FolderStatus.cpp b/src/vmime/net/pop3/POP3FolderStatus.cpp new file mode 100644 index 00000000..944379ac --- /dev/null +++ b/src/vmime/net/pop3/POP3FolderStatus.cpp @@ -0,0 +1,88 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + + +#include "vmime/net/pop3/POP3FolderStatus.hpp" + + +namespace vmime { +namespace net { +namespace pop3 { + + +POP3FolderStatus::POP3FolderStatus() + : m_count(0), + m_unseen(0) +{ +} + + +POP3FolderStatus::POP3FolderStatus(const POP3FolderStatus& other) + : folderStatus(), + m_count(other.m_count), + m_unseen(other.m_unseen) +{ +} + + +unsigned int POP3FolderStatus::getMessageCount() const +{ + return m_count; +} + + +unsigned int POP3FolderStatus::getUnseenCount() const +{ + return m_unseen; +} + + +void POP3FolderStatus::setMessageCount(const unsigned int count) +{ + m_count = count; +} + + +void POP3FolderStatus::setUnseenCount(const unsigned int unseen) +{ + m_unseen = unseen; +} + + +shared_ptr <folderStatus> POP3FolderStatus::clone() const +{ + return make_shared <POP3FolderStatus>(*this); +} + + +} // pop3 +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 diff --git a/src/vmime/net/pop3/POP3FolderStatus.hpp b/src/vmime/net/pop3/POP3FolderStatus.hpp new file mode 100644 index 00000000..70ba48b6 --- /dev/null +++ b/src/vmime/net/pop3/POP3FolderStatus.hpp @@ -0,0 +1,76 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_POP3_POP3FOLDERSTATUS_HPP_INCLUDED +#define VMIME_NET_POP3_POP3FOLDERSTATUS_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + + +#include "vmime/net/folderStatus.hpp" + + +namespace vmime { +namespace net { +namespace pop3 { + + +/** Holds the status of a POP3 folder. + */ + +class VMIME_EXPORT POP3FolderStatus : public folderStatus +{ +public: + + POP3FolderStatus(); + POP3FolderStatus(const POP3FolderStatus& other); + + // Inherited from folderStatus + unsigned int getMessageCount() const; + unsigned int getUnseenCount() const; + + shared_ptr <folderStatus> clone() const; + + + void setMessageCount(const unsigned int count); + void setUnseenCount(const unsigned int unseen); + +private: + + unsigned int m_count; + unsigned int m_unseen; +}; + + +} // pop3 +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + +#endif // VMIME_NET_POP3_POP3FOLDERSTATUS_HPP_INCLUDED diff --git a/src/vmime/net/pop3/POP3Message.cpp b/src/vmime/net/pop3/POP3Message.cpp new file mode 100644 index 00000000..08523611 --- /dev/null +++ b/src/vmime/net/pop3/POP3Message.cpp @@ -0,0 +1,250 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + + +#include "vmime/net/pop3/POP3Message.hpp" +#include "vmime/net/pop3/POP3Command.hpp" +#include "vmime/net/pop3/POP3Response.hpp" +#include "vmime/net/pop3/POP3Folder.hpp" +#include "vmime/net/pop3/POP3Store.hpp" + +#include "vmime/utility/outputStreamAdapter.hpp" +#include "vmime/utility/outputStreamStringAdapter.hpp" + +#include <sstream> + + +namespace vmime { +namespace net { +namespace pop3 { + + +POP3Message::POP3Message(shared_ptr <POP3Folder> folder, const int num) + : m_folder(folder), m_num(num), m_size(-1), m_deleted(false) +{ + folder->registerMessage(this); +} + + +POP3Message::~POP3Message() +{ + shared_ptr <POP3Folder> folder = m_folder.lock(); + + if (folder) + folder->unregisterMessage(this); +} + + +void POP3Message::onFolderClosed() +{ + m_folder.reset(); +} + + +int POP3Message::getNumber() const +{ + return (m_num); +} + + +const message::uid POP3Message::getUID() const +{ + return (m_uid); +} + + +size_t POP3Message::getSize() const +{ + if (m_size == static_cast <size_t>(-1)) + throw exceptions::unfetched_object(); + + return (m_size); +} + + +bool POP3Message::isExpunged() const +{ + return (false); +} + + +int POP3Message::getFlags() const +{ + int flags = FLAG_RECENT; + + if (m_deleted) + flags |= FLAG_DELETED; + + return (flags); +} + + +shared_ptr <const messageStructure> POP3Message::getStructure() const +{ + throw exceptions::operation_not_supported(); +} + + +shared_ptr <messageStructure> POP3Message::getStructure() +{ + throw exceptions::operation_not_supported(); +} + + +shared_ptr <const header> POP3Message::getHeader() const +{ + if (m_header == NULL) + throw exceptions::unfetched_object(); + + return (m_header); +} + + +void POP3Message::extract + (utility::outputStream& os, + utility::progressListener* progress, + const size_t start, const size_t length, + const bool /* peek */) const +{ + shared_ptr <const POP3Folder> folder = m_folder.lock(); + + if (!folder) + throw exceptions::illegal_state("Folder closed"); + else if (!folder->getStore()) + throw exceptions::illegal_state("Store disconnected"); + + if (start != 0 && length != static_cast <size_t>(-1)) + throw exceptions::partial_fetch_not_supported(); + + // Emit the "RETR" command + shared_ptr <POP3Store> store = constCast <POP3Folder>(folder)->m_store.lock(); + + POP3Command::RETR(m_num)->send(store->getConnection()); + + try + { + POP3Response::readLargeResponse + (store->getConnection(), os, progress, m_size); + } + catch (exceptions::command_error& e) + { + throw exceptions::command_error("RETR", e.response()); + } +} + + +void POP3Message::extractPart + (shared_ptr <const messagePart> /* p */, + utility::outputStream& /* os */, + utility::progressListener* /* progress */, + const size_t /* start */, const size_t /* length */, + const bool /* peek */) const +{ + throw exceptions::operation_not_supported(); +} + + +void POP3Message::fetchPartHeader(shared_ptr <messagePart> /* p */) +{ + throw exceptions::operation_not_supported(); +} + + +void POP3Message::fetch(shared_ptr <POP3Folder> msgFolder, const fetchAttributes& options) +{ + shared_ptr <POP3Folder> folder = m_folder.lock(); + + if (folder != msgFolder) + throw exceptions::folder_not_found(); + + // STRUCTURE and FLAGS attributes are not supported by POP3 + if (options.has(fetchAttributes::STRUCTURE | fetchAttributes::FLAGS)) + throw exceptions::operation_not_supported(); + + // Check for the real need to fetch the full header + static const int optionsRequiringHeader = + fetchAttributes::ENVELOPE | fetchAttributes::CONTENT_INFO | + fetchAttributes::FULL_HEADER | fetchAttributes::IMPORTANCE; + + if (!options.has(optionsRequiringHeader)) + return; + + // No need to differenciate between ENVELOPE, CONTENT_INFO, ... + // since POP3 only permits to retrieve the whole header and not + // fields in particular. + + // Emit the "TOP" command + shared_ptr <POP3Store> store = folder->m_store.lock(); + + POP3Command::TOP(m_num, 0)->send(store->getConnection()); + + try + { + string buffer; + utility::outputStreamStringAdapter bufferStream(buffer); + + POP3Response::readLargeResponse(store->getConnection(), + bufferStream, /* progress */ NULL, /* predictedSize */ 0); + + m_header = make_shared <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(); +} + + +shared_ptr <vmime::message> POP3Message::getParsedMessage() +{ + std::ostringstream oss; + utility::outputStreamAdapter os(oss); + + extract(os); + + shared_ptr <vmime::message> msg = make_shared <vmime::message>(); + msg->parse(oss.str()); + + return msg; +} + + +} // pop3 +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + diff --git a/src/vmime/net/pop3/POP3Message.hpp b/src/vmime/net/pop3/POP3Message.hpp new file mode 100644 index 00000000..87e71ba7 --- /dev/null +++ b/src/vmime/net/pop3/POP3Message.hpp @@ -0,0 +1,121 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_POP3_POP3MESSAGE_HPP_INCLUDED +#define VMIME_NET_POP3_POP3MESSAGE_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + + +#include "vmime/net/message.hpp" +#include "vmime/net/folder.hpp" + + +namespace vmime { +namespace net { +namespace pop3 { + + +class POP3Folder; + + +/** POP3 message implementation. + */ + +class VMIME_EXPORT POP3Message : public message +{ +private: + + friend class POP3Folder; + + POP3Message(const POP3Message&); + +public: + + POP3Message(shared_ptr <POP3Folder> folder, const int num); + + ~POP3Message(); + + + int getNumber() const; + + const uid getUID() const; + + size_t getSize() const; + + bool isExpunged() const; + + shared_ptr <const messageStructure> getStructure() const; + shared_ptr <messageStructure> getStructure(); + + shared_ptr <const header> getHeader() const; + + int getFlags() const; + void setFlags(const int flags, const int mode = FLAG_MODE_SET); + + void extract + (utility::outputStream& os, + utility::progressListener* progress = NULL, + const size_t start = 0, const size_t length = -1, + const bool peek = false) const; + + void extractPart + (shared_ptr <const messagePart> p, + utility::outputStream& os, + utility::progressListener* progress = NULL, + const size_t start = 0, const size_t length = -1, + const bool peek = false) const; + + void fetchPartHeader(shared_ptr <messagePart> p); + + shared_ptr <vmime::message> getParsedMessage(); + +private: + + void fetch(shared_ptr <POP3Folder> folder, const fetchAttributes& options); + + void onFolderClosed(); + + weak_ptr <POP3Folder> m_folder; + int m_num; + uid m_uid; + size_t m_size; + + bool m_deleted; + + shared_ptr <header> m_header; +}; + + +} // pop3 +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + +#endif // VMIME_NET_POP3_POP3MESSAGE_HPP_INCLUDED diff --git a/src/vmime/net/pop3/POP3Response.cpp b/src/vmime/net/pop3/POP3Response.cpp new file mode 100644 index 00000000..1dc5ee76 --- /dev/null +++ b/src/vmime/net/pop3/POP3Response.cpp @@ -0,0 +1,428 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + + +#include "vmime/net/pop3/POP3Response.hpp" +#include "vmime/net/pop3/POP3Connection.hpp" + +#include "vmime/platform.hpp" + +#include "vmime/utility/stringUtils.hpp" +#include "vmime/utility/filteredStream.hpp" +#include "vmime/utility/stringUtils.hpp" +#include "vmime/utility/inputStreamSocketAdapter.hpp" + +#include "vmime/net/socket.hpp" +#include "vmime/net/timeoutHandler.hpp" + + +namespace vmime { +namespace net { +namespace pop3 { + + +POP3Response::POP3Response(shared_ptr <socket> sok, shared_ptr <timeoutHandler> toh) + : m_socket(sok), m_timeoutHandler(toh) +{ +} + + +// static +shared_ptr <POP3Response> POP3Response::readResponse(shared_ptr <POP3Connection> conn) +{ + shared_ptr <POP3Response> resp = shared_ptr <POP3Response> + (new POP3Response(conn->getSocket(), conn->getTimeoutHandler())); + + string buffer; + resp->readResponseImpl(buffer, /* multiLine */ false); + + resp->m_firstLine = buffer; + resp->m_code = getResponseCode(buffer); + stripResponseCode(buffer, resp->m_text); + + return resp; +} + + +// static +shared_ptr <POP3Response> POP3Response::readMultilineResponse(shared_ptr <POP3Connection> conn) +{ + shared_ptr <POP3Response> resp = shared_ptr <POP3Response> + (new POP3Response(conn->getSocket(), conn->getTimeoutHandler())); + + string buffer; + resp->readResponseImpl(buffer, /* multiLine */ true); + + string firstLine, nextLines; + stripFirstLine(buffer, nextLines, &firstLine); + + resp->m_firstLine = firstLine; + resp->m_code = getResponseCode(firstLine); + stripResponseCode(firstLine, resp->m_text); + + std::istringstream iss(nextLines); + string line; + + while (std::getline(iss, line, '\n')) + resp->m_lines.push_back(utility::stringUtils::trim(line)); + + return resp; +} + + +// static +shared_ptr <POP3Response> POP3Response::readLargeResponse + (shared_ptr <POP3Connection> conn, utility::outputStream& os, + utility::progressListener* progress, const size_t predictedSize) +{ + shared_ptr <POP3Response> resp = shared_ptr <POP3Response> + (new POP3Response(conn->getSocket(), conn->getTimeoutHandler())); + + string firstLine; + resp->readResponseImpl(firstLine, os, progress, predictedSize); + + resp->m_firstLine = firstLine; + resp->m_code = getResponseCode(firstLine); + stripResponseCode(firstLine, resp->m_text); + + return resp; +} + + +bool POP3Response::isSuccess() const +{ + return m_code == CODE_OK; +} + + +const string POP3Response::getFirstLine() const +{ + return m_firstLine; +} + + +POP3Response::ResponseCode POP3Response::getCode() const +{ + return m_code; +} + + +const string POP3Response::getText() const +{ + return m_text; +} + + +const string POP3Response::getLineAt(const size_t pos) const +{ + return m_lines[pos]; +} + + +size_t POP3Response::getLineCount() const +{ + return m_lines.size(); +} + + +void POP3Response::readResponseImpl(string& buffer, const bool multiLine) +{ + bool foundTerminator = false; + + if (m_timeoutHandler) + m_timeoutHandler->resetTimeOut(); + + buffer.clear(); + + char last1 = '\0', last2 = '\0'; + + 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(); + + m_timeoutHandler->resetTimeOut(); + } + + // Receive data from the socket + string receiveBuffer; + m_socket->receive(receiveBuffer); + + if (receiveBuffer.empty()) // buffer is empty + { + platform::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 char 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 (size_t trans ; + string::npos != (trans = receiveBuffer.find("\n..")) ; ) + { + receiveBuffer.replace(trans, 3, "\n."); + } + + last1 = receiveBuffer[receiveBuffer.length() - 1]; + last2 = static_cast <char>((receiveBuffer.length() >= 2) ? receiveBuffer[receiveBuffer.length() - 2] : 0); + + // Append the data to the response buffer + buffer += receiveBuffer; + + // Check for terminator string (and strip it if present) + foundTerminator = checkTerminator(buffer, multiLine); + + // 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); + } + } +} + + +void POP3Response::readResponseImpl + (string& firstLine, utility::outputStream& os, + utility::progressListener* progress, const size_t predictedSize) +{ + size_t 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 + byte_t buffer[65536]; + const size_t read = is.read(buffer, sizeof(buffer)); + + if (read == 0) // buffer is empty + { + platform::getHandler()->wait(); + continue; + } + + // We have received data: reset the time-out counter + if (m_timeoutHandler) + m_timeoutHandler->resetTimeOut(); + + // Notify progress + current += read; + + if (progress) + { + total = std::max(total, current); + progress->progress(current, total); + } + + // If we don't have extracted the response code yet + if (!codeDone) + { + vmime::utility::stringUtils::appendBytesToString(temp, buffer, read); + + string responseData; + + if (stripFirstLine(temp, responseData, &firstLine) == true) + { + if (getResponseCode(firstLine) != CODE_OK) + throw exceptions::command_error("?", firstLine); + + codeDone = true; + + os.write(responseData.data(), responseData.length()); + temp.clear(); + + continue; + } + } + else + { + // Inject the data into the output stream + os.write(buffer, read); + } + } + + if (progress) + progress->stop(total); +} + + +// static +bool POP3Response::stripFirstLine + (const string& buffer, string& result, string* firstLine) +{ + const size_t end = buffer.find('\n'); + + if (end != string::npos) + { + if (firstLine) *firstLine = utility::stringUtils::trim(buffer.substr(0, end)); + result = buffer.substr(end + 1); + return true; + } + else + { + if (firstLine) *firstLine = utility::stringUtils::trim(buffer); + result = ""; + return false; + } +} + + +// static +POP3Response::ResponseCode POP3Response::getResponseCode(const string& buffer) +{ + if (buffer.length() >= 2) + { + // +[space] + if (buffer[0] == '+' && + (buffer[1] == ' ' || buffer[1] == '\t')) + { + return CODE_READY; + } + + // +OK + if (buffer.length() >= 3) + { + if (buffer[0] == '+' && + (buffer[1] == 'O' || buffer[1] == 'o') && + (buffer[2] == 'K' || buffer[1] == 'k')) + { + return CODE_OK; + } + } + } + + // -ERR or whatever + return CODE_ERR; +} + + +// static +void POP3Response::stripResponseCode(const string& buffer, string& result) +{ + const size_t pos = buffer.find_first_of(" \t"); + + if (pos != string::npos) + result = buffer.substr(pos + 1); + else + result = buffer; +} + + +// static +bool POP3Response::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; +} + + +// static +bool POP3Response::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; +} + + +} // pop3 +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 diff --git a/src/vmime/net/pop3/POP3Response.hpp b/src/vmime/net/pop3/POP3Response.hpp new file mode 100644 index 00000000..20477b5e --- /dev/null +++ b/src/vmime/net/pop3/POP3Response.hpp @@ -0,0 +1,182 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_SMTP_POP3RESPONSE_HPP_INCLUDED +#define VMIME_NET_SMTP_POP3RESPONSE_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + + +#include "vmime/object.hpp" +#include "vmime/base.hpp" + +#include "vmime/utility/outputStream.hpp" +#include "vmime/utility/progressListener.hpp" + +#include "vmime/net/socket.hpp" + + +namespace vmime { +namespace net { + + +class timeoutHandler; + + +namespace pop3 { + + +class POP3Connection; + + +/** A POP3 response, as sent by the server. + */ +class VMIME_EXPORT POP3Response : public object +{ +public: + + /** Possible response codes. */ + enum ResponseCode + { + CODE_OK = 0, + CODE_READY, + CODE_ERR + }; + + + /** Receive and parse a POP3 response from the + * specified connection. + * + * @param conn connection from which to read + * @return POP3 response + * @throws exceptions::operation_timed_out if no data + * has been received within the granted time + */ + static shared_ptr <POP3Response> readResponse(shared_ptr <POP3Connection> conn); + + /** Receive and parse a multiline POP3 response from + * the specified connection. + * + * @param conn connection from which to read + * @return POP3 response + * @throws exceptions::operation_timed_out if no data + * has been received within the granted time + */ + static shared_ptr <POP3Response> readMultilineResponse(shared_ptr <POP3Connection> conn); + + /** Receive and parse a large POP3 response (eg. message data) + * from the specified connection. + * + * @param conn connection from which to read + * @param os output stream to which response data will be written + * @param progress progress listener (can be NULL) + * @param predictedSize estimated size of response data (in bytes) + * @return POP3 response + * @throws exceptions::operation_timed_out if no data + * has been received within the granted time + */ + static shared_ptr <POP3Response> readLargeResponse + (shared_ptr <POP3Connection> conn, utility::outputStream& os, + utility::progressListener* progress, const size_t predictedSize); + + + /** Returns whether the response is successful ("OK"). + * + * @return true if the response if successful, false otherwise + */ + bool isSuccess() const; + + /** Return the POP3 response code. + * + * @return response code + */ + ResponseCode getCode() const; + + /** Return the POP3 response text (first line). + * + * @return response text + */ + const string getText() const; + + /** Return the first POP3 response line. + * + * @return first response line + */ + const string getFirstLine() const; + + /** Return the response line at the specified position. + * + * @param pos line index + * @return line at the specified index + */ + const string getLineAt(const size_t pos) const; + + /** Return the number of lines in the response. + * + * @return number of lines in the response + */ + size_t getLineCount() const; + +private: + + POP3Response(shared_ptr <socket> sok, shared_ptr <timeoutHandler> toh); + + void readResponseImpl(string& buffer, const bool multiLine); + void readResponseImpl + (string& firstLine, utility::outputStream& os, + utility::progressListener* progress, const size_t predictedSize); + + + static bool stripFirstLine(const string& buffer, string& result, string* firstLine); + + static ResponseCode getResponseCode(const string& buffer); + + static void stripResponseCode(const string& buffer, string& result); + + static bool checkTerminator(string& buffer, const bool multiLine); + static bool checkOneTerminator(string& buffer, const string& term); + + + shared_ptr <socket> m_socket; + shared_ptr <timeoutHandler> m_timeoutHandler; + + string m_firstLine; + ResponseCode m_code; + string m_text; + + std::vector <string> m_lines; +}; + + +} // pop3 +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + +#endif // VMIME_NET_SMTP_POP3RESPONSE_HPP_INCLUDED diff --git a/src/vmime/net/pop3/POP3SStore.cpp b/src/vmime/net/pop3/POP3SStore.cpp new file mode 100644 index 00000000..f1c3da74 --- /dev/null +++ b/src/vmime/net/pop3/POP3SStore.cpp @@ -0,0 +1,79 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + + +#include "vmime/net/pop3/POP3SStore.hpp" + + +namespace vmime { +namespace net { +namespace pop3 { + + +POP3SStore::POP3SStore(shared_ptr <session> sess, shared_ptr <security::authenticator> auth) + : POP3Store(sess, auth, true) +{ +} + + +POP3SStore::~POP3SStore() +{ +} + + +const string POP3SStore::getProtocolName() const +{ + return "pop3s"; +} + + + +// Service infos + +POP3ServiceInfos POP3SStore::sm_infos(true); + + +const serviceInfos& POP3SStore::getInfosInstance() +{ + return sm_infos; +} + + +const serviceInfos& POP3SStore::getInfos() const +{ + return sm_infos; +} + + +} // pop3 +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + diff --git a/src/vmime/net/pop3/POP3SStore.hpp b/src/vmime/net/pop3/POP3SStore.hpp new file mode 100644 index 00000000..e60b4ef8 --- /dev/null +++ b/src/vmime/net/pop3/POP3SStore.hpp @@ -0,0 +1,71 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_POP3_POP3SSTORE_HPP_INCLUDED +#define VMIME_NET_POP3_POP3SSTORE_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + + +#include "vmime/net/pop3/POP3Store.hpp" + + +namespace vmime { +namespace net { +namespace pop3 { + + +/** POP3S store service. + */ + +class VMIME_EXPORT POP3SStore : public POP3Store +{ +public: + + POP3SStore(shared_ptr <session> sess, shared_ptr <security::authenticator> auth); + ~POP3SStore(); + + const string getProtocolName() const; + + static const serviceInfos& getInfosInstance(); + const serviceInfos& getInfos() const; + +private: + + static POP3ServiceInfos sm_infos; +}; + + +} // pop3 +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + +#endif // VMIME_NET_POP3_POP3SSTORE_HPP_INCLUDED + diff --git a/src/vmime/net/pop3/POP3ServiceInfos.cpp b/src/vmime/net/pop3/POP3ServiceInfos.cpp new file mode 100644 index 00000000..4760d4f2 --- /dev/null +++ b/src/vmime/net/pop3/POP3ServiceInfos.cpp @@ -0,0 +1,143 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + + +#include "vmime/net/pop3/POP3ServiceInfos.hpp" + + +namespace vmime { +namespace net { +namespace pop3 { + + +POP3ServiceInfos::POP3ServiceInfos(const bool pop3s) + : m_pop3s(pop3s) +{ +} + + +const string POP3ServiceInfos::getPropertyPrefix() const +{ + if (m_pop3s) + return "store.pop3s."; + else + return "store.pop3."; +} + + +const POP3ServiceInfos::props& POP3ServiceInfos::getProperties() const +{ + static props pop3Props = + { + // POP3-specific options + property("options.apop", serviceInfos::property::TYPE_BOOLEAN, "true"), + property("options.apop.fallback", serviceInfos::property::TYPE_BOOLEAN, "true"), +#if VMIME_HAVE_SASL_SUPPORT + property("options.sasl", serviceInfos::property::TYPE_BOOLEAN, "true"), + property("options.sasl.fallback", serviceInfos::property::TYPE_BOOLEAN, "true"), +#endif // VMIME_HAVE_SASL_SUPPORT + + // Common properties + property(serviceInfos::property::AUTH_USERNAME, serviceInfos::property::FLAG_REQUIRED), + property(serviceInfos::property::AUTH_PASSWORD, serviceInfos::property::FLAG_REQUIRED), + +#if VMIME_HAVE_TLS_SUPPORT + property(serviceInfos::property::CONNECTION_TLS), + property(serviceInfos::property::CONNECTION_TLS_REQUIRED), +#endif // VMIME_HAVE_TLS_SUPPORT + + property(serviceInfos::property::SERVER_ADDRESS, serviceInfos::property::FLAG_REQUIRED), + property(serviceInfos::property::SERVER_PORT, "110"), + }; + + static props pop3sProps = + { + // POP3-specific options + property("options.apop", serviceInfos::property::TYPE_BOOLEAN, "true"), + property("options.apop.fallback", serviceInfos::property::TYPE_BOOLEAN, "true"), +#if VMIME_HAVE_SASL_SUPPORT + property("options.sasl", serviceInfos::property::TYPE_BOOLEAN, "true"), + property("options.sasl.fallback", serviceInfos::property::TYPE_BOOLEAN, "true"), +#endif // VMIME_HAVE_SASL_SUPPORT + + // Common properties + property(serviceInfos::property::AUTH_USERNAME, serviceInfos::property::FLAG_REQUIRED), + property(serviceInfos::property::AUTH_PASSWORD, serviceInfos::property::FLAG_REQUIRED), + +#if VMIME_HAVE_TLS_SUPPORT + property(serviceInfos::property::CONNECTION_TLS), + property(serviceInfos::property::CONNECTION_TLS_REQUIRED), +#endif // VMIME_HAVE_TLS_SUPPORT + + property(serviceInfos::property::SERVER_ADDRESS, serviceInfos::property::FLAG_REQUIRED), + property(serviceInfos::property::SERVER_PORT, "995"), + }; + + return m_pop3s ? pop3sProps : pop3Props; +} + + +const std::vector <serviceInfos::property> POP3ServiceInfos::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); +#if VMIME_HAVE_SASL_SUPPORT + list.push_back(p.PROPERTY_OPTIONS_SASL); + list.push_back(p.PROPERTY_OPTIONS_SASL_FALLBACK); +#endif // VMIME_HAVE_SASL_SUPPORT + + // Common properties + list.push_back(p.PROPERTY_AUTH_USERNAME); + list.push_back(p.PROPERTY_AUTH_PASSWORD); + +#if VMIME_HAVE_TLS_SUPPORT + if (!m_pop3s) + { + list.push_back(p.PROPERTY_CONNECTION_TLS); + list.push_back(p.PROPERTY_CONNECTION_TLS_REQUIRED); + } +#endif // VMIME_HAVE_TLS_SUPPORT + + list.push_back(p.PROPERTY_SERVER_ADDRESS); + list.push_back(p.PROPERTY_SERVER_PORT); + + return list; +} + + +} // pop3 +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + diff --git a/src/vmime/net/pop3/POP3ServiceInfos.hpp b/src/vmime/net/pop3/POP3ServiceInfos.hpp new file mode 100644 index 00000000..710d8be3 --- /dev/null +++ b/src/vmime/net/pop3/POP3ServiceInfos.hpp @@ -0,0 +1,93 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_POP3_POP3SERVICEINFOS_HPP_INCLUDED +#define VMIME_NET_POP3_POP3SERVICEINFOS_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + + +#include "vmime/net/serviceInfos.hpp" + + +namespace vmime { +namespace net { +namespace pop3 { + + +/** Information about POP3 service. + */ + +class VMIME_EXPORT POP3ServiceInfos : public serviceInfos +{ +public: + + POP3ServiceInfos(const bool pop3s); + + struct props + { + // POP3-specific options + serviceInfos::property PROPERTY_OPTIONS_APOP; + serviceInfos::property PROPERTY_OPTIONS_APOP_FALLBACK; +#if VMIME_HAVE_SASL_SUPPORT + serviceInfos::property PROPERTY_OPTIONS_SASL; + serviceInfos::property PROPERTY_OPTIONS_SASL_FALLBACK; +#endif // VMIME_HAVE_SASL_SUPPORT + + // Common properties + serviceInfos::property PROPERTY_AUTH_USERNAME; + serviceInfos::property PROPERTY_AUTH_PASSWORD; + +#if VMIME_HAVE_TLS_SUPPORT + serviceInfos::property PROPERTY_CONNECTION_TLS; + serviceInfos::property PROPERTY_CONNECTION_TLS_REQUIRED; +#endif // VMIME_HAVE_TLS_SUPPORT + + serviceInfos::property PROPERTY_SERVER_ADDRESS; + serviceInfos::property PROPERTY_SERVER_PORT; + }; + + const props& getProperties() const; + + const string getPropertyPrefix() const; + const std::vector <serviceInfos::property> getAvailableProperties() const; + +private: + + const bool m_pop3s; +}; + + +} // pop3 +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + +#endif // VMIME_NET_POP3_POP3SERVICEINFOS_HPP_INCLUDED + diff --git a/src/vmime/net/pop3/POP3Store.cpp b/src/vmime/net/pop3/POP3Store.cpp new file mode 100644 index 00000000..e6e95b1b --- /dev/null +++ b/src/vmime/net/pop3/POP3Store.cpp @@ -0,0 +1,240 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + + +#include "vmime/net/pop3/POP3Store.hpp" +#include "vmime/net/pop3/POP3Folder.hpp" +#include "vmime/net/pop3/POP3Command.hpp" +#include "vmime/net/pop3/POP3Response.hpp" + +#include "vmime/exception.hpp" + +#include <algorithm> + + +namespace vmime { +namespace net { +namespace pop3 { + + +POP3Store::POP3Store(shared_ptr <session> sess, shared_ptr <security::authenticator> auth, const bool secured) + : store(sess, getInfosInstance(), auth), m_isPOP3S(secured) +{ +} + + +POP3Store::~POP3Store() +{ + try + { + if (isConnected()) + disconnect(); + } + catch (vmime::exception&) + { + // Ignore + } +} + + +const string POP3Store::getProtocolName() const +{ + return "pop3"; +} + + +shared_ptr <folder> POP3Store::getDefaultFolder() +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return make_shared <POP3Folder> + (folder::path(folder::path::component("INBOX")), + dynamicCast <POP3Store>(shared_from_this())); +} + + +shared_ptr <folder> POP3Store::getRootFolder() +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return make_shared <POP3Folder> + (folder::path(), dynamicCast <POP3Store>(shared_from_this())); +} + + +shared_ptr <folder> POP3Store::getFolder(const folder::path& path) +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return make_shared <POP3Folder> + (path, dynamicCast <POP3Store>(shared_from_this())); +} + + +bool POP3Store::isValidFolderName(const folder::path::component& /* name */) const +{ + return true; +} + + +void POP3Store::connect() +{ + if (isConnected()) + throw exceptions::already_connected(); + + m_connection = make_shared <POP3Connection> + (dynamicCast <POP3Store>(shared_from_this()), getAuthenticator()); + + try + { + m_connection->connect(); + } + catch (std::exception&) + { + m_connection = null; + throw; + } +} + + +bool POP3Store::isPOP3S() const +{ + return m_isPOP3S; +} + + +bool POP3Store::isConnected() const +{ + return m_connection && m_connection->isConnected(); +} + + +bool POP3Store::isSecuredConnection() const +{ + if (m_connection == NULL) + return false; + + return m_connection->isSecuredConnection(); +} + + +shared_ptr <connectionInfos> POP3Store::getConnectionInfos() const +{ + if (m_connection == NULL) + return null; + + return m_connection->getConnectionInfos(); +} + + +shared_ptr <POP3Connection> POP3Store::getConnection() +{ + return m_connection; +} + + +void POP3Store::disconnect() +{ + if (!isConnected()) + throw exceptions::not_connected(); + + for (std::list <POP3Folder*>::iterator it = m_folders.begin() ; + it != m_folders.end() ; ++it) + { + (*it)->onStoreDisconnected(); + } + + m_folders.clear(); + + + m_connection->disconnect(); + m_connection = null; +} + + +void POP3Store::noop() +{ + if (!m_connection) + throw exceptions::not_connected(); + + POP3Command::NOOP()->send(m_connection); + + shared_ptr <POP3Response> response = POP3Response::readResponse(m_connection); + + if (!response->isSuccess()) + throw exceptions::command_error("NOOP", response->getFirstLine()); +} + + +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); +} + + +int POP3Store::getCapabilities() const +{ + return (CAPABILITY_DELETE_MESSAGE); +} + + + +// Service infos + +POP3ServiceInfos POP3Store::sm_infos(false); + + +const serviceInfos& POP3Store::getInfosInstance() +{ + return sm_infos; +} + + +const serviceInfos& POP3Store::getInfos() const +{ + return sm_infos; +} + + +} // pop3 +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + diff --git a/src/vmime/net/pop3/POP3Store.hpp b/src/vmime/net/pop3/POP3Store.hpp new file mode 100644 index 00000000..b35659a0 --- /dev/null +++ b/src/vmime/net/pop3/POP3Store.hpp @@ -0,0 +1,116 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_POP3_POP3STORE_HPP_INCLUDED +#define VMIME_NET_POP3_POP3STORE_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + + +#include "vmime/net/store.hpp" + +#include "vmime/net/pop3/POP3ServiceInfos.hpp" +#include "vmime/net/pop3/POP3Connection.hpp" + +#include "vmime/utility/stream.hpp" + + +namespace vmime { +namespace net { +namespace pop3 { + + +class POP3Folder; +class POP3Command; +class POP3Response; + + +/** POP3 store service. + */ + +class VMIME_EXPORT POP3Store : public store +{ + friend class POP3Folder; + friend class POP3Message; + +public: + + POP3Store(shared_ptr <session> sess, shared_ptr <security::authenticator> auth, const bool secured = false); + ~POP3Store(); + + const string getProtocolName() const; + + shared_ptr <folder> getDefaultFolder(); + shared_ptr <folder> getRootFolder(); + shared_ptr <folder> getFolder(const folder::path& path); + + bool isValidFolderName(const folder::path::component& name) const; + + static const serviceInfos& getInfosInstance(); + const serviceInfos& getInfos() const; + + void connect(); + bool isConnected() const; + void disconnect(); + + void noop(); + + int getCapabilities() const; + + bool isSecuredConnection() const; + shared_ptr <connectionInfos> getConnectionInfos() const; + shared_ptr <POP3Connection> getConnection(); + + bool isPOP3S() const; + +private: + + shared_ptr <POP3Connection> m_connection; + + + void registerFolder(POP3Folder* folder); + void unregisterFolder(POP3Folder* folder); + + std::list <POP3Folder*> m_folders; + + + const bool m_isPOP3S; + + + // Service infos + static POP3ServiceInfos sm_infos; +}; + + +} // pop3 +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + +#endif // VMIME_NET_POP3_POP3STORE_HPP_INCLUDED diff --git a/src/vmime/net/pop3/POP3Utils.cpp b/src/vmime/net/pop3/POP3Utils.cpp new file mode 100644 index 00000000..7ba65fff --- /dev/null +++ b/src/vmime/net/pop3/POP3Utils.cpp @@ -0,0 +1,114 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + + +#include "vmime/net/pop3/POP3Utils.hpp" +#include "vmime/net/pop3/POP3Response.hpp" + +#include <sstream> + + +namespace vmime { +namespace net { +namespace pop3 { + + +// static +void POP3Utils::parseMultiListOrUidlResponse(shared_ptr <POP3Response> response, std::map <int, string>& result) +{ + std::map <int, string> ids; + + for (size_t i = 0, n = response->getLineCount() ; i < n ; ++i) + { + string line = response->getLineAt(i); + 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()))); + } + } + } +} + + + +class POP3MessageSetEnumerator : public messageSetEnumerator +{ +public: + + void enumerateNumberMessageRange(const vmime::net::numberMessageRange& range) + { + for (int i = range.getFirst(), last = range.getLast() ; i <= last ; ++i) + list.push_back(i); + } + + void enumerateUIDMessageRange(const vmime::net::UIDMessageRange& /* range */) + { + // Not supported + } + +public: + + std::vector <int> list; +}; + + +// static +const std::vector <int> POP3Utils::messageSetToNumberList(const messageSet& msgs) +{ + POP3MessageSetEnumerator en; + msgs.enumerate(en); + + return en.list; +} + + +} // pop3 +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + diff --git a/src/vmime/net/pop3/POP3Utils.hpp b/src/vmime/net/pop3/POP3Utils.hpp new file mode 100644 index 00000000..c7459efe --- /dev/null +++ b/src/vmime/net/pop3/POP3Utils.hpp @@ -0,0 +1,86 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_POP3_POP3UTILS_HPP_INCLUDED +#define VMIME_NET_POP3_POP3UTILS_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + + +#include <map> + +#include "vmime/types.hpp" + +#include "vmime/net/messageSet.hpp" + + +namespace vmime { +namespace net { +namespace pop3 { + + +class POP3Response; + + +class VMIME_EXPORT POP3Utils +{ +public: + + /** Parse a response of type ([integer] [string] \n)*. + * This is used in LIST or UIDL commands: + * + * C: UIDL + * S: +OK + * S: 1 whqtswO00WBw418f9t5JxYwZ + * S: 2 QhdPYR:00WBw1Ph7x7 + * S: . + * + * @param response raw response string as returned by the server + * @param result points to an associative array which maps a message + * number to its corresponding data (either UID or size) + */ + static void parseMultiListOrUidlResponse + (shared_ptr <POP3Response> response, std::map <int, string>& result); + + /** Returns a list of message numbers given a message set. + * + * @param msgs message set + * @return list of message numbers + */ + static const std::vector <int> messageSetToNumberList(const messageSet& msgs); +}; + + +} // pop3 +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 + +#endif // VMIME_NET_POP3_POP3UTILS_HPP_INCLUDED + diff --git a/src/vmime/net/pop3/pop3.hpp b/src/vmime/net/pop3/pop3.hpp new file mode 100644 index 00000000..366b1e4a --- /dev/null +++ b/src/vmime/net/pop3/pop3.hpp @@ -0,0 +1,35 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_POP3_POP3_HPP_INCLUDED +#define VMIME_NET_POP3_POP3_HPP_INCLUDED + + +#include "vmime/net/pop3/POP3Folder.hpp" +#include "vmime/net/pop3/POP3FolderStatus.hpp" +#include "vmime/net/pop3/POP3Message.hpp" +#include "vmime/net/pop3/POP3Store.hpp" +#include "vmime/net/pop3/POP3SStore.hpp" + + +#endif // VMIME_NET_POP3_POP3_HPP_INCLUDED diff --git a/src/vmime/net/securedConnectionInfos.hpp b/src/vmime/net/securedConnectionInfos.hpp new file mode 100644 index 00000000..8ed8b138 --- /dev/null +++ b/src/vmime/net/securedConnectionInfos.hpp @@ -0,0 +1,55 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_SECUREDCONNECTIONINFOS_HPP_INCLUDED +#define VMIME_NET_SECUREDCONNECTIONINFOS_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#include "vmime/net/connectionInfos.hpp" + + +namespace vmime { +namespace net { + + +/** Information about the secured connection used by a service. + */ +class VMIME_EXPORT securedConnectionInfos : public connectionInfos +{ +}; + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + +#endif // VMIME_NET_SECUREDCONNECTIONINFOS_HPP_INCLUDED + diff --git a/src/vmime/net/sendmail/sendmail.hpp b/src/vmime/net/sendmail/sendmail.hpp new file mode 100644 index 00000000..b3692526 --- /dev/null +++ b/src/vmime/net/sendmail/sendmail.hpp @@ -0,0 +1,31 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_SENDMAIL_SENDMAIL_HPP_INCLUDED +#define VMIME_NET_SENDMAIL_SENDMAIL_HPP_INCLUDED + + +#include "vmime/net/sendmail/sendmailTransport.hpp" + + +#endif // VMIME_NET_SENDMAIL_SENDMAIL_HPP_INCLUDED diff --git a/src/vmime/net/sendmail/sendmailServiceInfos.cpp b/src/vmime/net/sendmail/sendmailServiceInfos.cpp new file mode 100644 index 00000000..21cac00c --- /dev/null +++ b/src/vmime/net/sendmail/sendmailServiceInfos.cpp @@ -0,0 +1,78 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SENDMAIL + + +#include "vmime/net/sendmail/sendmailServiceInfos.hpp" + + +namespace vmime { +namespace net { +namespace sendmail { + + +sendmailServiceInfos::sendmailServiceInfos() +{ +} + + +const string sendmailServiceInfos::getPropertyPrefix() const +{ + return "transport.sendmail."; +} + + +const sendmailServiceInfos::props& sendmailServiceInfos::getProperties() const +{ + static props sendmailProps = + { + // Path to sendmail (override default) + property("binpath", serviceInfos::property::TYPE_STRING, string(VMIME_SENDMAIL_PATH)) + }; + + return sendmailProps; +} + + +const std::vector <serviceInfos::property> sendmailServiceInfos::getAvailableProperties() const +{ + std::vector <property> list; + const props& p = getProperties(); + + list.push_back(p.PROPERTY_BINPATH); + + return list; +} + + +} // sendmail +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SENDMAIL + diff --git a/src/vmime/net/sendmail/sendmailServiceInfos.hpp b/src/vmime/net/sendmail/sendmailServiceInfos.hpp new file mode 100644 index 00000000..de94e392 --- /dev/null +++ b/src/vmime/net/sendmail/sendmailServiceInfos.hpp @@ -0,0 +1,71 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_SENDMAIL_SENDMAILSERVICEINFOS_HPP_INCLUDED +#define VMIME_NET_SENDMAIL_SENDMAILSERVICEINFOS_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SENDMAIL + + +#include "vmime/net/serviceInfos.hpp" + + +namespace vmime { +namespace net { +namespace sendmail { + + +/** Information about sendmail service. + */ + +class VMIME_EXPORT sendmailServiceInfos : public serviceInfos +{ +public: + + sendmailServiceInfos(); + + struct props + { + serviceInfos::property PROPERTY_BINPATH; + }; + + const props& getProperties() const; + + const string getPropertyPrefix() const; + const std::vector <serviceInfos::property> getAvailableProperties() const; +}; + + +} // sendmail +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SENDMAIL + +#endif // VMIME_NET_SENDMAIL_SENDMAILSERVICEINFOS_HPP_INCLUDED + diff --git a/src/vmime/net/sendmail/sendmailTransport.cpp b/src/vmime/net/sendmail/sendmailTransport.cpp new file mode 100644 index 00000000..8ef18e3b --- /dev/null +++ b/src/vmime/net/sendmail/sendmailTransport.cpp @@ -0,0 +1,230 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SENDMAIL + + +#include "vmime/net/sendmail/sendmailTransport.hpp" + +#include "vmime/exception.hpp" +#include "vmime/platform.hpp" +#include "vmime/message.hpp" +#include "vmime/mailboxList.hpp" + +#include "vmime/utility/filteredStream.hpp" +#include "vmime/utility/childProcess.hpp" + +#include "vmime/utility/streamUtils.hpp" + +#include "vmime/net/defaultConnectionInfos.hpp" + +#include "vmime/config.hpp" + + +// Helpers for service properties +#define GET_PROPERTY(type, prop) \ + (getInfos().getPropertyValue <type>(getSession(), \ + dynamic_cast <const sendmailServiceInfos&>(getInfos()).getProperties().prop)) +#define HAS_PROPERTY(prop) \ + (getInfos().hasProperty(getSession(), \ + dynamic_cast <const sendmailServiceInfos&>(getInfos()).getProperties().prop)) + + +namespace vmime { +namespace net { +namespace sendmail { + + +sendmailTransport::sendmailTransport(shared_ptr <session> sess, shared_ptr <security::authenticator> auth) + : transport(sess, getInfosInstance(), auth), m_connected(false) +{ +} + + +sendmailTransport::~sendmailTransport() +{ + try + { + if (isConnected()) + disconnect(); + } + catch (vmime::exception&) + { + // Ignore + } +} + + +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; +} + + +bool sendmailTransport::isConnected() const +{ + return (m_connected); +} + + +bool sendmailTransport::isSecuredConnection() const +{ + return false; +} + + +shared_ptr <connectionInfos> sendmailTransport::getConnectionInfos() const +{ + return make_shared <defaultConnectionInfos>("localhost", static_cast <port_t>(0)); +} + + +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 size_t size, + utility::progressListener* progress, const mailbox& sender) +{ + // 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"); + + if (!sender.isEmpty()) + args.push_back(expeditor.getEmail().generate()); + else + args.push_back(sender.getEmail().generate()); + + args.push_back("--"); + + for (size_t i = 0 ; i < recipients.getMailboxCount() ; ++i) + args.push_back(recipients.getMailboxAt(i)->getEmail().generate()); + + // 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 size_t size, utility::progressListener* progress) +{ + const utility::file::path path = vmime::platform::getHandler()-> + getFileSystemFactory()->stringToPath(m_sendmailPath); + + shared_ptr <utility::childProcess> proc = + vmime::platform::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 + +sendmailServiceInfos sendmailTransport::sm_infos; + + +const serviceInfos& sendmailTransport::getInfosInstance() +{ + return sm_infos; +} + + +const serviceInfos& sendmailTransport::getInfos() const +{ + return sm_infos; +} + + +} // sendmail +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SENDMAIL + diff --git a/src/vmime/net/sendmail/sendmailTransport.hpp b/src/vmime/net/sendmail/sendmailTransport.hpp new file mode 100644 index 00000000..d1c6aec0 --- /dev/null +++ b/src/vmime/net/sendmail/sendmailTransport.hpp @@ -0,0 +1,103 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_SENDMAIL_SENDMAILTRANSPORT_HPP_INCLUDED +#define VMIME_NET_SENDMAIL_SENDMAILTRANSPORT_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SENDMAIL + + +#include "vmime/net/transport.hpp" +#include "vmime/net/socket.hpp" +#include "vmime/net/timeoutHandler.hpp" + +#include "vmime/net/sendmail/sendmailServiceInfos.hpp" + + +namespace vmime { +namespace net { +namespace sendmail { + + +/** Sendmail local transport service. + */ + +class VMIME_EXPORT sendmailTransport : public transport +{ +public: + + sendmailTransport(shared_ptr <session> sess, shared_ptr <security::authenticator> auth); + ~sendmailTransport(); + + const string getProtocolName() const; + + static const serviceInfos& getInfosInstance(); + const serviceInfos& getInfos() const; + + void connect(); + bool isConnected() const; + void disconnect(); + + void noop(); + + void send + (const mailbox& expeditor, + const mailboxList& recipients, + utility::inputStream& is, + const size_t size, + utility::progressListener* progress = NULL, + const mailbox& sender = mailbox()); + + bool isSecuredConnection() const; + shared_ptr <connectionInfos> getConnectionInfos() const; + +private: + + void internalDisconnect(); + + void internalSend(const std::vector <string> args, utility::inputStream& is, + const size_t size, utility::progressListener* progress); + + + string m_sendmailPath; + + bool m_connected; + + + // Service infos + static sendmailServiceInfos sm_infos; +}; + + +} // sendmail +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SENDMAIL + +#endif // VMIME_NET_SENDMAIL_SENDMAILTRANSPORT_HPP_INCLUDED diff --git a/src/vmime/net/service.cpp b/src/vmime/net/service.cpp new file mode 100644 index 00000000..c52ba592 --- /dev/null +++ b/src/vmime/net/service.cpp @@ -0,0 +1,152 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#include "vmime/net/service.hpp" + +#include "vmime/platform.hpp" + +#if VMIME_HAVE_SASL_SUPPORT + #include "vmime/security/sasl/defaultSASLAuthenticator.hpp" +#else + #include "vmime/security/defaultAuthenticator.hpp" +#endif // VMIME_HAVE_SASL_SUPPORT + +#if VMIME_HAVE_TLS_SUPPORT + #include "vmime/security/cert/defaultCertificateVerifier.hpp" +#endif // VMIME_HAVE_TLS_SUPPORT + + +namespace vmime { +namespace net { + + +service::service(shared_ptr <session> sess, const serviceInfos& /* infos */, + shared_ptr <security::authenticator> auth) + : m_session(sess), m_auth(auth) +{ + if (!auth) + { +#if VMIME_HAVE_SASL_SUPPORT + m_auth = make_shared + <security::sasl::defaultSASLAuthenticator>(); +#else + m_auth = make_shared + <security::defaultAuthenticator>(); +#endif // VMIME_HAVE_SASL_SUPPORT + } + +#if VMIME_HAVE_TLS_SUPPORT + m_certVerifier = make_shared <security::cert::defaultCertificateVerifier>(); +#endif // VMIME_HAVE_TLS_SUPPORT + + m_socketFactory = platform::getHandler()->getSocketFactory(); +} + + +service::~service() +{ +} + + +shared_ptr <const session> service::getSession() const +{ + return (m_session); +} + + +shared_ptr <session> service::getSession() +{ + return (m_session); +} + + +shared_ptr <const security::authenticator> service::getAuthenticator() const +{ + return (m_auth); +} + + +shared_ptr <security::authenticator> service::getAuthenticator() +{ + return (m_auth); +} + + +void service::setAuthenticator(shared_ptr <security::authenticator> auth) +{ + m_auth = auth; +} + + +#if VMIME_HAVE_TLS_SUPPORT + +void service::setCertificateVerifier(shared_ptr <security::cert::certificateVerifier> cv) +{ + m_certVerifier = cv; +} + + +shared_ptr <security::cert::certificateVerifier> service::getCertificateVerifier() +{ + return m_certVerifier; +} + +#endif // VMIME_HAVE_TLS_SUPPORT + + +void service::setSocketFactory(shared_ptr <socketFactory> sf) +{ + m_socketFactory = sf; +} + + +shared_ptr <socketFactory> service::getSocketFactory() +{ + return m_socketFactory; +} + + +void service::setTimeoutHandlerFactory(shared_ptr <timeoutHandlerFactory> thf) +{ + m_toHandlerFactory = thf; +} + + +shared_ptr <timeoutHandlerFactory> service::getTimeoutHandlerFactory() +{ + return m_toHandlerFactory; +} + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + diff --git a/src/vmime/net/service.hpp b/src/vmime/net/service.hpp new file mode 100644 index 00000000..6969ac20 --- /dev/null +++ b/src/vmime/net/service.hpp @@ -0,0 +1,233 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_SERVICE_HPP_INCLUDED +#define VMIME_NET_SERVICE_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#include "vmime/types.hpp" + +#include "vmime/net/session.hpp" + +#include "vmime/net/serviceInfos.hpp" +#include "vmime/net/connectionInfos.hpp" + +#include "vmime/net/socket.hpp" +#include "vmime/net/timeoutHandler.hpp" + +#if VMIME_HAVE_TLS_SUPPORT + #include "vmime/security/cert/certificateVerifier.hpp" +#endif // VMIME_HAVE_TLS_SUPPORT + +#include "vmime/utility/progressListener.hpp" + + +namespace vmime { +namespace net { + + +/** Base class for messaging services. + */ + +class VMIME_EXPORT service : public object +{ +protected: + + service(shared_ptr <session> sess, const serviceInfos& infos, shared_ptr <security::authenticator> auth); + +public: + + virtual ~service(); + + /** Possible service types. */ + enum Type + { + TYPE_STORE = 0, /**< The service is a message store. */ + TYPE_TRANSPORT /**< The service sends messages. */ + }; + + /** Return the type of service. + * + * @return type of service + */ + virtual Type getType() const = 0; + + /** Return the protocol name of this service. + * + * @return protocol name + */ + virtual const string getProtocolName() const = 0; + + /** Return the session object associated with this service instance. + * + * @return session object + */ + shared_ptr <const session> getSession() const; + + /** Return the session object associated with this service instance. + * + * @return session object + */ + shared_ptr <session> getSession(); + + /** Return information about this service. + * + * @return information about the service + */ + virtual const serviceInfos& getInfos() const = 0; + + /** Connect to service. + */ + virtual void connect() = 0; + + /** Disconnect from service. + */ + virtual void disconnect() = 0; + + /** Test whether this service is connected. + * + * @return true if the service is connected, false otherwise + */ + virtual bool isConnected() const = 0; + + /** Do nothing but ensure the server do not disconnect (for + * example, this can reset the auto-logout timer on the + * server, if one exists). + */ + virtual void noop() = 0; + + /** Return the authenticator object used with this service instance. + * + * @return authenticator object + */ + shared_ptr <const security::authenticator> getAuthenticator() const; + + /** Return the authenticator object used with this service instance. + * + * @return authenticator object + */ + shared_ptr <security::authenticator> getAuthenticator(); + + /** Set the authenticator object used with this service instance. + * + * @param auth authenticator object + */ + void setAuthenticator(shared_ptr <security::authenticator> auth); + +#if VMIME_HAVE_TLS_SUPPORT + + /** Set the object responsible for verifying certificates when + * using secured connections (TLS/SSL). + */ + void setCertificateVerifier(shared_ptr <security::cert::certificateVerifier> cv); + + /** Get the object responsible for verifying certificates when + * using secured connections (TLS/SSL). + */ + shared_ptr <security::cert::certificateVerifier> getCertificateVerifier(); + +#endif // VMIME_HAVE_TLS_SUPPORT + + /** Set the factory used to create socket objects for this + * service. + * + * @param sf socket factory + */ + void setSocketFactory(shared_ptr <socketFactory> sf); + + /** Return the factory used to create socket objects for this + * service. + * + * @return socket factory + */ + shared_ptr <socketFactory> getSocketFactory(); + + /** Set the factory used to create timeoutHandler objects for + * this service. By default, no timeout handler is used. Not all + * services support timeout handling. + * + * @param thf timeoutHandler factory + */ + void setTimeoutHandlerFactory(shared_ptr <timeoutHandlerFactory> thf); + + /** Return the factory used to create timeoutHandler objects for + * this service. + * + * @return timeoutHandler factory + */ + shared_ptr <timeoutHandlerFactory> getTimeoutHandlerFactory(); + + /** Set a property for this service (service prefix is added automatically). + * + * WARNING: this sets the property on the session object, so all service + * instances created with the session object will inherit the property. + * + * @param name property name + * @param value property value + */ + template <typename TYPE> + void setProperty(const string& name, const TYPE& value) + { + m_session->getProperties()[getInfos().getPropertyPrefix() + name] = value; + } + + /** Check whether the connection is secured. + * + * @return true if the connection is secured, false otherwise + */ + virtual bool isSecuredConnection() const = 0; + + /** Get information about the connection. + * + * @return information about the connection + */ + virtual shared_ptr <connectionInfos> getConnectionInfos() const = 0; + +private: + + shared_ptr <session> m_session; + shared_ptr <security::authenticator> m_auth; + +#if VMIME_HAVE_TLS_SUPPORT + shared_ptr <security::cert::certificateVerifier> m_certVerifier; +#endif // VMIME_HAVE_TLS_SUPPORT + + shared_ptr <socketFactory> m_socketFactory; + + shared_ptr <timeoutHandlerFactory> m_toHandlerFactory; +}; + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + +#endif // VMIME_NET_SERVICE_HPP_INCLUDED diff --git a/src/vmime/net/serviceFactory.cpp b/src/vmime/net/serviceFactory.cpp new file mode 100644 index 00000000..bbc9944a --- /dev/null +++ b/src/vmime/net/serviceFactory.cpp @@ -0,0 +1,145 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#include "vmime/net/serviceFactory.hpp" +#include "vmime/net/service.hpp" + +#include "vmime/exception.hpp" + +#include "vmime/net/builtinServices.inl" + + +namespace vmime { +namespace net { + + +serviceFactory::serviceFactory() +{ +} + + +serviceFactory::~serviceFactory() +{ +} + + +shared_ptr <serviceFactory> serviceFactory::getInstance() +{ + static serviceFactory instance; + return shared_ptr <serviceFactory>(&instance, noop_shared_ptr_deleter <serviceFactory>()); +} + + +shared_ptr <service> serviceFactory::create + (shared_ptr <session> sess, const string& protocol, + shared_ptr <security::authenticator> auth) +{ + return (getServiceByProtocol(protocol)->create(sess, auth)); +} + + +shared_ptr <service> serviceFactory::create + (shared_ptr <session> sess, const utility::url& u, + shared_ptr <security::authenticator> auth) +{ + shared_ptr <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); +} + + +shared_ptr <const serviceFactory::registeredService> serviceFactory::getServiceByProtocol(const string& protocol) const +{ + const string name(utility::stringUtils::toLower(protocol)); + + for (std::vector <shared_ptr <registeredService> >::const_iterator it = m_services.begin() ; + it != m_services.end() ; ++it) + { + if ((*it)->getName() == name) + return (*it); + } + + return null; +} + + +size_t serviceFactory::getServiceCount() const +{ + return (m_services.size()); +} + + +shared_ptr <const serviceFactory::registeredService> serviceFactory::getServiceAt(const size_t pos) const +{ + return (m_services[pos]); +} + + +const std::vector <shared_ptr <const serviceFactory::registeredService> > serviceFactory::getServiceList() const +{ + std::vector <shared_ptr <const registeredService> > res; + + for (std::vector <shared_ptr <registeredService> >::const_iterator it = m_services.begin() ; + it != m_services.end() ; ++it) + { + res.push_back(*it); + } + + return (res); +} + + +void serviceFactory::registerService(shared_ptr <registeredService> reg) +{ + m_services.push_back(reg); +} + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + diff --git a/src/vmime/net/serviceFactory.hpp b/src/vmime/net/serviceFactory.hpp new file mode 100644 index 00000000..9295b345 --- /dev/null +++ b/src/vmime/net/serviceFactory.hpp @@ -0,0 +1,165 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_SERVICEFACTORY_HPP_INCLUDED +#define VMIME_NET_SERVICEFACTORY_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#include <map> + +#include "vmime/types.hpp" +#include "vmime/base.hpp" + +#include "vmime/utility/stringUtils.hpp" +#include "vmime/utility/url.hpp" + +#include "vmime/net/service.hpp" +#include "vmime/net/serviceInfos.hpp" +#include "vmime/net/timeoutHandler.hpp" + +#include "vmime/security/authenticator.hpp" + +#include "vmime/utility/progressListener.hpp" + + +namespace vmime { +namespace net { + + +class session; + + +/** A factory to create 'service' objects for a specified protocol. + */ + +class VMIME_EXPORT serviceFactory +{ +private: + + serviceFactory(); + ~serviceFactory(); + +public: + + static shared_ptr <serviceFactory> getInstance(); + + /** Information about a registered service. */ + class registeredService : public object + { + friend class serviceFactory; + + protected: + + virtual ~registeredService() { } + + public: + + virtual shared_ptr <service> create + (shared_ptr <session> sess, + shared_ptr <security::authenticator> auth) const = 0; + + virtual int getType() const = 0; + virtual const string& getName() const = 0; + virtual const serviceInfos& getInfos() const = 0; + }; + + + /** Register a new service by its protocol name. + * + * @param reg service registration infos + */ + void registerService(shared_ptr <registeredService> reg); + + /** Create a new service instance from a protocol name. + * + * @param sess session + * @param protocol protocol name (eg. "pop3") + * @param auth authenticator used to provide credentials (can be NULL if not used) + * @return a new service instance for the specified protocol, or NULL if no service + * is registered for this protocol + */ + shared_ptr <service> create + (shared_ptr <session> sess, + const string& protocol, + shared_ptr <security::authenticator> auth = null); + + /** Create a new service instance from a URL. + * + * @param sess session + * @param u full URL with at least protocol and server (you can also specify + * port, username and password) + * @param auth authenticator used to provide credentials (can be NULL if not used) + * @return a new service instance for the specified protocol or NULL if no service + * is registered for this protocol + */ + shared_ptr <service> create + (shared_ptr <session> sess, + const utility::url& u, + shared_ptr <security::authenticator> auth = null); + + /** Return information about a registered protocol. + * + * @param protocol protocol name + * @return information about this protocol, or NULL if no service is registered + * for this protocol + */ + shared_ptr <const registeredService> getServiceByProtocol(const string& protocol) const; + + /** Return the number of registered services. + * + * @return number of registered services + */ + size_t getServiceCount() const; + + /** Return the registered service at the specified position. + * + * @param pos position of the registered service to return + * @return registered service at the specified position + */ + shared_ptr <const registeredService> getServiceAt(const size_t pos) const; + + /** Return a list of all registered services. + * + * @return list of registered services + */ + const std::vector <shared_ptr <const registeredService> > getServiceList() const; + +private: + + std::vector <shared_ptr <registeredService> > m_services; +}; + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + +#endif // VMIME_NET_SERVICEFACTORY_HPP_INCLUDED diff --git a/src/vmime/net/serviceInfos.cpp b/src/vmime/net/serviceInfos.cpp new file mode 100644 index 00000000..8de0529e --- /dev/null +++ b/src/vmime/net/serviceInfos.cpp @@ -0,0 +1,167 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#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::AUTH_USERNAME + ("auth.username", serviceInfos::property::TYPE_STRING); + +const serviceInfos::property serviceInfos::property::AUTH_PASSWORD + ("auth.password", serviceInfos::property::TYPE_STRING); + +#if VMIME_HAVE_TLS_SUPPORT + +const serviceInfos::property serviceInfos::property::CONNECTION_TLS + ("connection.tls", serviceInfos::property::TYPE_BOOLEAN, "false"); + +const serviceInfos::property serviceInfos::property::CONNECTION_TLS_REQUIRED + ("connection.tls.required", serviceInfos::property::TYPE_BOOLEAN, "false"); + +#endif // VMIME_HAVE_TLS_SUPPORT + + + +// serviceInfos + +serviceInfos::serviceInfos() +{ +} + + +serviceInfos::serviceInfos(const serviceInfos&) +{ +} + + +serviceInfos& serviceInfos::operator=(const serviceInfos&) +{ + return (*this); +} + + +serviceInfos::~serviceInfos() +{ +} + + +bool serviceInfos::hasProperty(shared_ptr <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); +} + + +serviceInfos::property::Types serviceInfos::property::getType() const +{ + return (m_type); +} + + +int serviceInfos::property::getFlags() const +{ + return (m_flags); +} + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + diff --git a/src/vmime/net/serviceInfos.hpp b/src/vmime/net/serviceInfos.hpp new file mode 100644 index 00000000..6e3209ca --- /dev/null +++ b/src/vmime/net/serviceInfos.hpp @@ -0,0 +1,246 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_SERVICEINFOS_HPP_INCLUDED +#define VMIME_NET_SERVICEINFOS_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#include <vector> + +#include "vmime/types.hpp" + +#include "vmime/net/session.hpp" + + +namespace vmime { +namespace net { + + +/** Stores information about a messaging service. + */ + +class VMIME_EXPORT serviceInfos +{ + friend class serviceFactory; + +protected: + + serviceInfos(); + serviceInfos(const serviceInfos&); + +private: + + serviceInfos& operator=(const serviceInfos&); + +public: + + virtual ~serviceInfos(); + + + /** A service property. + */ + class property + { + public: + + /** The common property 'server.address' which is + * the host name or the IP address of the server. */ + static const property SERVER_ADDRESS; + + /** The common property 'server.port' which is + * the port used to connect to the server. */ + static const property SERVER_PORT; + + /** The common property 'server.rootpath' which is + * the full path of the folder on the server (for + * maildir, this is the local filesystem directory). */ + static const property SERVER_ROOTPATH; + + /** The common property 'auth.username' which is the + * username used to authenticate with the server. */ + static const property AUTH_USERNAME; + + /** The common property 'auth.password' which is the + * password used to authenticate with the server. */ + static const property AUTH_PASSWORD; + +#if VMIME_HAVE_TLS_SUPPORT + + /** The common property 'connection.tls': this is used to + * start a secured connection if it is supported by the + * server (STARTTLS extension). + */ + static const property CONNECTION_TLS; + + /** The common property 'connection.tls.required' should be + * set to 'true' to make the connection process fail if the + * server can't start a secured connection (no effect if + * 'connection.tls' is not set to 'true'). + */ + static const property CONNECTION_TLS_REQUIRED; + +#endif // VMIME_HAVE_TLS_SUPPORT + + + /** Value types. + */ + enum Types + { + TYPE_INTEGER, /*< Integer number. */ + TYPE_STRING, /*< Character string. */ + TYPE_BOOLEAN, /*< Boolean (true or false). */ + + TYPE_DEFAULT = TYPE_STRING + }; + + /** Property flags. + */ + enum Flags + { + FLAG_NONE = 0, /*< No flags. */ + FLAG_REQUIRED = (1 << 0), /*< The property must be valued. */ + FLAG_HIDDEN = (1 << 1), /*< The property should not be shown + to the user but can be modified. */ + + FLAG_DEFAULT = FLAG_NONE /*< Default flags. */ + }; + + + /** Construct a new property. + * + * @param name property name + * @param type value type + * @param defaultValue default value + * @param flags property attributes + */ + property(const string& name, const Types type, const string& defaultValue = "", const int flags = FLAG_DEFAULT); + + /** Construct a new property from an existing property. + * + * @param p source property + * @param addFlags flags to add + * @param removeFlags flags to remove + */ + property(const property& p, const int addFlags = FLAG_NONE, const int removeFlags = FLAG_NONE); + + /** Construct a new property from an existing property. + * + * @param p source property + * @param newDefaultValue new default value + * @param addFlags flags to add + * @param removeFlags flags to remove + */ + property(const property& p, const string& newDefaultValue, const int addFlags = FLAG_NONE, const int removeFlags = FLAG_NONE); + + property& operator=(const property& p); + + /** Return the name of the property. + * + * @return property name + */ + const string& getName() const; + + /** Return the default value of the property or + * an empty string if there is no default value. + * + * @return default value for the property + */ + const string& getDefaultValue() const; + + /** Return the value type of the property. + * + * @return property value type + */ + Types getType() const; + + /** Return the attributes of the property (see + * serviceInfos::property::Types constants). + * + * @return property attributes + */ + int getFlags() const; + + private: + + string m_name; + string m_defaultValue; + Types m_type; + int m_flags; + }; + + + /** Return the property prefix used by this service. + * Use this to set/get properties in the session object. + * + * @return property prefix + */ + virtual const string getPropertyPrefix() const = 0; + + /** Return a list of available properties for this service. + * + * @return list of properties + */ + virtual const std::vector <property> getAvailableProperties() const = 0; + + /** Helper function to retrieve the value of a property. + * + * @param s session object + * @param p property to retrieve + * @throw exceptions::no_such_property if the property does not exist + * and has the flag property::FLAG_REQUIRED + * @return value of the property + */ + template <typename TYPE> + const TYPE getPropertyValue(shared_ptr <session> s, const property& p) const + { + if (p.getFlags() & property::FLAG_REQUIRED) + return s->getProperties()[getPropertyPrefix() + p.getName()].template getValue <TYPE>(); + + return s->getProperties().template getProperty <TYPE>(getPropertyPrefix() + p.getName(), + propertySet::valueFromString <TYPE>(p.getDefaultValue())); + } + + /** Helper function to test if the specified property is set in + * the session object. + * + * @param s session object + * @param p property to test + * @return true if the property is set, false otherwise + */ + bool hasProperty(shared_ptr <session> s, const property& p) const; +}; + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + +#endif // VMIME_NET_SERVICEINFOS_HPP_INCLUDED diff --git a/src/vmime/net/serviceRegistration.inl b/src/vmime/net/serviceRegistration.inl new file mode 100644 index 00000000..2366fe01 --- /dev/null +++ b/src/vmime/net/serviceRegistration.inl @@ -0,0 +1,98 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/net/serviceFactory.hpp" + + +#ifndef VMIME_BUILDING_DOC + + +namespace vmime { +namespace net { + + +template <class S> +class registeredServiceImpl : public serviceFactory::registeredService +{ +public: + + registeredServiceImpl(const string& name, const int type) + : m_type(type), m_name(name), m_servInfos(S::getInfosInstance()) + { + } + + shared_ptr <service> create + (shared_ptr <session> sess, + shared_ptr <security::authenticator> auth) const + { + return make_shared <S>(sess, auth); + } + + const serviceInfos& getInfos() const + { + return (m_servInfos); + } + + const string& getName() const + { + return (m_name); + } + + int getType() const + { + return (m_type); + } + +private: + + const int m_type; + const string m_name; + const serviceInfos& m_servInfos; +}; + + +// Basic service registerer +template <class S> +class serviceRegisterer +{ +public: + + serviceRegisterer(const string& protocol, const service::Type type) + { + serviceFactory::getInstance()->registerService + (make_shared <registeredServiceImpl <S> >(protocol, type)); + } +}; + + +} // net +} // vmime + + +#define REGISTER_SERVICE(p_class, p_name, p_type) \ + vmime::net::serviceRegisterer <vmime::net::p_class> \ + p_name(#p_name, vmime::net::service::p_type) + + +#endif // VMIME_BUILDING_DOC + diff --git a/src/vmime/net/session.cpp b/src/vmime/net/session.cpp new file mode 100644 index 00000000..36b9f2c3 --- /dev/null +++ b/src/vmime/net/session.cpp @@ -0,0 +1,158 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#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() + : m_tlsProps(make_shared <tls::TLSProperties>()) +{ +} + + +session::session(const session& sess) + : object(), m_props(sess.m_props), + m_tlsProps(make_shared <tls::TLSProperties>(*sess.m_tlsProps)) +{ +} + + +session::session(const propertySet& props) + : m_props(props), m_tlsProps(make_shared <tls::TLSProperties>()) +{ +} + + +session::~session() +{ +} + + +shared_ptr <transport> session::getTransport(shared_ptr <security::authenticator> auth) +{ + return (getTransport(m_props["transport.protocol"], auth)); +} + + +shared_ptr <transport> session::getTransport + (const string& protocol, shared_ptr <security::authenticator> auth) +{ + shared_ptr <session> sess(dynamicCast <session>(shared_from_this())); + shared_ptr <service> sv = serviceFactory::getInstance()->create(sess, protocol, auth); + + if (!sv || sv->getType() != service::TYPE_TRANSPORT) + return null; + + return dynamicCast <transport>(sv); +} + + +shared_ptr <transport> session::getTransport + (const utility::url& url, shared_ptr <security::authenticator> auth) +{ + shared_ptr <session> sess(dynamicCast <session>(shared_from_this())); + shared_ptr <service> sv = serviceFactory::getInstance()->create(sess, url, auth); + + if (!sv || sv->getType() != service::TYPE_TRANSPORT) + return null; + + return dynamicCast <transport>(sv); +} + + +shared_ptr <store> session::getStore(shared_ptr <security::authenticator> auth) +{ + return (getStore(m_props["store.protocol"], auth)); +} + + +shared_ptr <store> session::getStore + (const string& protocol, shared_ptr <security::authenticator> auth) +{ + shared_ptr <session> sess(dynamicCast <session>(shared_from_this())); + shared_ptr <service> sv = serviceFactory::getInstance()->create(sess, protocol, auth); + + if (!sv || sv->getType() != service::TYPE_STORE) + return null; + + return dynamicCast <store>(sv); +} + + +shared_ptr <store> session::getStore + (const utility::url& url, shared_ptr <security::authenticator> auth) +{ + shared_ptr <session> sess(dynamicCast <session>(shared_from_this())); + shared_ptr <service> sv = serviceFactory::getInstance()->create(sess, url, auth); + + if (!sv || sv->getType() != service::TYPE_STORE) + return null; + + return dynamicCast <store>(sv); +} + + +const propertySet& session::getProperties() const +{ + return (m_props); +} + + +propertySet& session::getProperties() +{ + return (m_props); +} + + +void session::setTLSProperties(shared_ptr <tls::TLSProperties> tlsProps) +{ + m_tlsProps = make_shared <tls::TLSProperties>(*tlsProps); +} + + +shared_ptr <tls::TLSProperties> session::getTLSProperties() const +{ + return m_tlsProps; +} + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + diff --git a/src/vmime/net/session.hpp b/src/vmime/net/session.hpp new file mode 100644 index 00000000..a7e0ea1a --- /dev/null +++ b/src/vmime/net/session.hpp @@ -0,0 +1,178 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_SESSION_HPP_INCLUDED +#define VMIME_NET_SESSION_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#include "vmime/security/authenticator.hpp" + +#include "vmime/net/tls/TLSProperties.hpp" + +#include "vmime/utility/url.hpp" + +#include "vmime/propertySet.hpp" + + +namespace vmime { +namespace net { + + +class store; +class transport; + + +/** An object that contains all the information needed + * for connection to a service. + */ + +class VMIME_EXPORT session : public object +{ +public: + + session(); + session(const session& sess); + session(const propertySet& props); + + virtual ~session(); + + /** Return a transport service instance for the protocol specified + * in the session properties. + * + * The property "transport.protocol" specify the protocol to use. + * + * @param auth authenticator object to use for the new transport service. If + * NULL, a default one is used. The default authenticator simply return user + * credentials by reading the session properties "auth.username" and "auth.password". + * @return a new transport service, or NULL if no service is registered for this + * protocol or is not a transport protocol + */ + shared_ptr <transport> getTransport + (shared_ptr <security::authenticator> auth = null); + + /** Return a transport service instance for the specified protocol. + * + * @param protocol transport protocol to use (eg. "smtp") + * @param auth authenticator object to use for the new transport service. If + * NULL, a default one is used. The default authenticator simply return user + * credentials by reading the session properties "auth.username" and "auth.password". + * @return a new transport service, or NULL if no service is registered for this + * protocol or is not a transport protocol + */ + shared_ptr <transport> getTransport + (const string& protocol, + shared_ptr <security::authenticator> auth = null); + + /** Return a transport service instance for the specified URL. + * + * @param url full URL with at least the protocol to use (eg: "smtp://myserver.com/") + * @param auth authenticator object to use for the new transport service. If + * NULL, a default one is used. The default authenticator simply return user + * credentials by reading the session properties "auth.username" and "auth.password". + * @return a new transport service, or NULL if no service is registered for this + * protocol or is not a transport protocol + */ + shared_ptr <transport> getTransport + (const utility::url& url, + shared_ptr <security::authenticator> auth = null); + + /** Return a transport service instance for the protocol specified + * in the session properties. + * + * The property "store.protocol" specify the protocol to use. + * + * @param auth authenticator object to use for the new store service. If + * NULL, a default one is used. The default authenticator simply return user + * credentials by reading the session properties "auth.username" and "auth.password". + * @return a new store service, or NULL if no service is registered for this + * protocol or is not a store protocol + */ + shared_ptr <store> getStore(shared_ptr <security::authenticator> auth = null); + + /** Return a store service instance for the specified protocol. + * + * @param protocol store protocol to use (eg. "imap") + * @param auth authenticator object to use for the new store service. If + * NULL, a default one is used. The default authenticator simply return user + * credentials by reading the session properties "auth.username" and "auth.password". + * @return a new store service, or NULL if no service is registered for this + * protocol or is not a store protocol + */ + shared_ptr <store> getStore + (const string& protocol, + shared_ptr <security::authenticator> auth = null); + + /** Return a store service instance for the specified URL. + * + * @param url full URL with at least the protocol to use (eg: "imap://username:[email protected]/") + * @param auth authenticator object to use for the new store service. If + * NULL, a default one is used. The default authenticator simply return user + * credentials by reading the session properties "auth.username" and "auth.password". + * @return a new store service, or NULL if no service is registered for this + * protocol or is not a store protocol + */ + shared_ptr <store> getStore + (const utility::url& url, + shared_ptr <security::authenticator> auth = null); + + /** Properties for the session and for the services. + */ + const propertySet& getProperties() const; + + /** Properties for the session and for the services. + */ + propertySet& getProperties(); + + /** Set properties for SSL/TLS secured connections in this session. + * + * @param tlsProps SSL/TLS properties + */ + void setTLSProperties(shared_ptr <tls::TLSProperties> tlsProps); + + /** Get properties for SSL/TLS secured connections in this session. + * + * @return SSL/TLS properties + */ + shared_ptr <tls::TLSProperties> getTLSProperties() const; + +private: + + propertySet m_props; + + shared_ptr <tls::TLSProperties> m_tlsProps; +}; + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + +#endif // VMIME_NET_SESSION_HPP_INCLUDED diff --git a/src/vmime/net/smtp/SMTPChunkingOutputStreamAdapter.cpp b/src/vmime/net/smtp/SMTPChunkingOutputStreamAdapter.cpp new file mode 100644 index 00000000..69f63bc9 --- /dev/null +++ b/src/vmime/net/smtp/SMTPChunkingOutputStreamAdapter.cpp @@ -0,0 +1,145 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + + +#include "vmime/net/smtp/SMTPChunkingOutputStreamAdapter.hpp" + +#include "vmime/net/smtp/SMTPConnection.hpp" +#include "vmime/net/smtp/SMTPTransport.hpp" + +#include <algorithm> + + +namespace vmime { +namespace net { +namespace smtp { + + +SMTPChunkingOutputStreamAdapter::SMTPChunkingOutputStreamAdapter(shared_ptr <SMTPConnection> conn) + : m_connection(conn), m_bufferSize(0), m_chunkCount(0) +{ +} + + +void SMTPChunkingOutputStreamAdapter::sendChunk + (const byte_t* const data, const size_t count, const bool last) +{ + if (count == 0 && !last) + { + // Nothing to send + return; + } + + // Send this chunk + m_connection->sendRequest(SMTPCommand::BDAT(count, last)); + m_connection->getSocket()->sendRaw(data, count); + + ++m_chunkCount; + + // If PIPELINING is not supported, read one response for this BDAT command + if (!m_connection->hasExtension("PIPELINING")) + { + shared_ptr <SMTPResponse> resp = m_connection->readResponse(); + + if (resp->getCode() != 250) + { + m_connection->getTransport()->disconnect(); + throw exceptions::command_error("BDAT", resp->getText()); + } + } + // If PIPELINING is supported, read one response for each chunk (ie. number + // of BDAT commands issued) after the last chunk has been sent + else if (last) + { + bool invalidReply = false; + shared_ptr <SMTPResponse> resp; + + for (unsigned int i = 0 ; i < m_chunkCount ; ++i) + { + resp = m_connection->readResponse(); + + if (resp->getCode() != 250) + invalidReply = true; + } + + if (invalidReply) + { + m_connection->getTransport()->disconnect(); + throw exceptions::command_error("BDAT", resp->getText()); + } + } +} + + +void SMTPChunkingOutputStreamAdapter::writeImpl + (const byte_t* const data, const size_t count) +{ + const byte_t* curData = data; + size_t curCount = count; + + while (curCount != 0) + { + // Fill the buffer + const size_t remaining = sizeof(m_buffer) - m_bufferSize; + const size_t bytesToCopy = std::min(remaining, curCount); + + std::copy(data, data + bytesToCopy, m_buffer + m_bufferSize); + + m_bufferSize += bytesToCopy; + curData += bytesToCopy; + curCount -= bytesToCopy; + + // If the buffer is full, send this chunk + if (m_bufferSize >= sizeof(m_buffer)) + { + sendChunk(m_buffer, m_bufferSize, /* last */ false); + m_bufferSize = 0; + } + } +} + + +void SMTPChunkingOutputStreamAdapter::flush() +{ + sendChunk(m_buffer, m_bufferSize, /* last */ true); + m_bufferSize = 0; +} + + +size_t SMTPChunkingOutputStreamAdapter::getBlockSize() +{ + return sizeof(m_buffer); +} + + +} // smtp +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP diff --git a/src/vmime/net/smtp/SMTPChunkingOutputStreamAdapter.hpp b/src/vmime/net/smtp/SMTPChunkingOutputStreamAdapter.hpp new file mode 100644 index 00000000..cfb3f50f --- /dev/null +++ b/src/vmime/net/smtp/SMTPChunkingOutputStreamAdapter.hpp @@ -0,0 +1,85 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_SMTP_SMTPCHUNKINGOUTPUTSTREAMADAPTER_HPP_INCLUDED +#define VMIME_NET_SMTP_SMTPCHUNKINGOUTPUTSTREAMADAPTER_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + + +#include "vmime/utility/outputStream.hpp" + + +namespace vmime { +namespace net { +namespace smtp { + + +class SMTPConnection; + + +/** An output stream adapter used with ESMTP CHUNKING extension. + */ +class VMIME_EXPORT SMTPChunkingOutputStreamAdapter : public utility::outputStream +{ +public: + + SMTPChunkingOutputStreamAdapter(shared_ptr <SMTPConnection> conn); + + void flush(); + + size_t getBlockSize(); + +protected: + + void writeImpl(const byte_t* const data, const size_t count); + +private: + + SMTPChunkingOutputStreamAdapter(const SMTPChunkingOutputStreamAdapter&); + + + void sendChunk(const byte_t* const data, const size_t count, const bool last); + + + shared_ptr <SMTPConnection> m_connection; + + byte_t m_buffer[262144]; // 256 KB + size_t m_bufferSize; + + unsigned int m_chunkCount; +}; + + +} // smtp +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + +#endif // VMIME_NET_SMTP_SMTPCHUNKINGOUTPUTSTREAMADAPTER_HPP_INCLUDED diff --git a/src/vmime/net/smtp/SMTPCommand.cpp b/src/vmime/net/smtp/SMTPCommand.cpp new file mode 100644 index 00000000..949ab0c1 --- /dev/null +++ b/src/vmime/net/smtp/SMTPCommand.cpp @@ -0,0 +1,214 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + + +#include "vmime/net/smtp/SMTPCommand.hpp" + +#include "vmime/net/socket.hpp" + +#include "vmime/mailbox.hpp" +#include "vmime/utility/outputStreamAdapter.hpp" + + +namespace vmime { +namespace net { +namespace smtp { + + +SMTPCommand::SMTPCommand(const string& text) + : m_text(text) +{ +} + + +// static +shared_ptr <SMTPCommand> SMTPCommand::EHLO(const string& hostname) +{ + std::ostringstream cmd; + cmd.imbue(std::locale::classic()); + cmd << "EHLO " << hostname; + + return createCommand(cmd.str()); +} + + +// static +shared_ptr <SMTPCommand> SMTPCommand::HELO(const string& hostname) +{ + std::ostringstream cmd; + cmd.imbue(std::locale::classic()); + cmd << "HELO " << hostname; + + return createCommand(cmd.str()); +} + + +// static +shared_ptr <SMTPCommand> SMTPCommand::AUTH(const string& mechName) +{ + std::ostringstream cmd; + cmd.imbue(std::locale::classic()); + cmd << "AUTH " << mechName; + + return createCommand(cmd.str()); +} + + +// static +shared_ptr <SMTPCommand> SMTPCommand::STARTTLS() +{ + return createCommand("STARTTLS"); +} + + +// static +shared_ptr <SMTPCommand> SMTPCommand::MAIL(const mailbox& mbox, const bool utf8) +{ + return MAIL(mbox, utf8, 0); +} + + +// static +shared_ptr <SMTPCommand> SMTPCommand::MAIL(const mailbox& mbox, const bool utf8, const size_t size) +{ + std::ostringstream cmd; + cmd.imbue(std::locale::classic()); + cmd << "MAIL FROM:<"; + + if (utf8) + { + cmd << mbox.getEmail().toText().getConvertedText(vmime::charsets::UTF_8); + } + else + { + vmime::utility::outputStreamAdapter cmd2(cmd); + mbox.getEmail().generate(cmd2); + } + + cmd << ">"; + + if (utf8) + cmd << " SMTPUTF8"; + + if (size != 0) + cmd << " SIZE=" << size; + + return createCommand(cmd.str()); +} + + +// static +shared_ptr <SMTPCommand> SMTPCommand::RCPT(const mailbox& mbox, const bool utf8) +{ + std::ostringstream cmd; + cmd.imbue(std::locale::classic()); + cmd << "RCPT TO:<"; + + if (utf8) + { + cmd << mbox.getEmail().toText().getConvertedText(vmime::charsets::UTF_8); + } + else + { + vmime::utility::outputStreamAdapter cmd2(cmd); + mbox.getEmail().generate(cmd2); + } + + cmd << ">"; + + return createCommand(cmd.str()); +} + + +// static +shared_ptr <SMTPCommand> SMTPCommand::RSET() +{ + return createCommand("RSET"); +} + + +// static +shared_ptr <SMTPCommand> SMTPCommand::DATA() +{ + return createCommand("DATA"); +} + + +// static +shared_ptr <SMTPCommand> SMTPCommand::BDAT(const size_t chunkSize, const bool last) +{ + std::ostringstream cmd; + cmd.imbue(std::locale::classic()); + cmd << "BDAT " << chunkSize; + + if (last) + cmd << " LAST"; + + return createCommand(cmd.str()); +} + + +// static +shared_ptr <SMTPCommand> SMTPCommand::NOOP() +{ + return createCommand("NOOP"); +} + + +// static +shared_ptr <SMTPCommand> SMTPCommand::QUIT() +{ + return createCommand("QUIT"); +} + + +// static +shared_ptr <SMTPCommand> SMTPCommand::createCommand(const string& text) +{ + return shared_ptr <SMTPCommand>(new SMTPCommand(text)); +} + + +const string SMTPCommand::getText() const +{ + return m_text; +} + + +void SMTPCommand::writeToSocket(shared_ptr <socket> sok) +{ + sok->send(m_text + "\r\n"); +} + + +} // smtp +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP diff --git a/src/vmime/net/smtp/SMTPCommand.hpp b/src/vmime/net/smtp/SMTPCommand.hpp new file mode 100644 index 00000000..dbb0888b --- /dev/null +++ b/src/vmime/net/smtp/SMTPCommand.hpp @@ -0,0 +1,111 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_SMTP_SMTPCOMMAND_HPP_INCLUDED +#define VMIME_NET_SMTP_SMTPCOMMAND_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + + +#include "vmime/object.hpp" +#include "vmime/base.hpp" + + +namespace vmime { + + +class mailbox; + + +namespace net { + + +class socket; +class timeoutHandler; + + +namespace smtp { + + +/** A SMTP command, as sent to server. + */ +class VMIME_EXPORT SMTPCommand : public object +{ +public: + + static shared_ptr <SMTPCommand> HELO(const string& hostname); + static shared_ptr <SMTPCommand> EHLO(const string& hostname); + static shared_ptr <SMTPCommand> AUTH(const string& mechName); + static shared_ptr <SMTPCommand> STARTTLS(); + static shared_ptr <SMTPCommand> MAIL(const mailbox& mbox, const bool utf8); + static shared_ptr <SMTPCommand> MAIL(const mailbox& mbox, const bool utf8, const size_t size); + static shared_ptr <SMTPCommand> RCPT(const mailbox& mbox, const bool utf8); + static shared_ptr <SMTPCommand> RSET(); + static shared_ptr <SMTPCommand> DATA(); + static shared_ptr <SMTPCommand> BDAT(const size_t chunkSize, const bool last); + static shared_ptr <SMTPCommand> NOOP(); + static shared_ptr <SMTPCommand> QUIT(); + + /** Creates a new SMTP command with the specified text. + * + * @param text command text + * @return a new SMTPCommand object + */ + static shared_ptr <SMTPCommand> createCommand(const string& text); + + /** Sends this command to the specified socket. + * + * @param sok socket to which the command will be written + */ + virtual void writeToSocket(shared_ptr <socket> sok); + + /** Returns the full text of the command, including command name + * and parameters (if any). + * + * @return command text (eg. "RCPT TO:<[email protected]>") + */ + virtual const string getText() const; + +protected: + + SMTPCommand(const string& text); + SMTPCommand(const SMTPCommand&); + +private: + + string m_text; +}; + + +} // smtp +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + +#endif // VMIME_NET_SMTP_SMTPCOMMAND_HPP_INCLUDED diff --git a/src/vmime/net/smtp/SMTPCommandSet.cpp b/src/vmime/net/smtp/SMTPCommandSet.cpp new file mode 100644 index 00000000..3e03427c --- /dev/null +++ b/src/vmime/net/smtp/SMTPCommandSet.cpp @@ -0,0 +1,144 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + + +#include "vmime/net/smtp/SMTPCommandSet.hpp" + +#include "vmime/net/socket.hpp" + +#include "vmime/mailbox.hpp" + +#include <stdexcept> + + +namespace vmime { +namespace net { +namespace smtp { + + +SMTPCommandSet::SMTPCommandSet(const bool pipeline) + : SMTPCommand(""), m_pipeline(pipeline), + m_started(false), m_lastCommandSent() +{ +} + + +// static +shared_ptr <SMTPCommandSet> SMTPCommandSet::create(const bool pipeline) +{ + return shared_ptr <SMTPCommandSet>(new SMTPCommandSet(pipeline)); +} + + +void SMTPCommandSet::addCommand(shared_ptr <SMTPCommand> cmd) +{ + if (m_started) + { + throw std::runtime_error("Could not add command to pipeline: " + "one or more commands have already been sent to the server."); + } + + m_commands.push_back(cmd); +} + + +void SMTPCommandSet::writeToSocket(shared_ptr <socket> sok) +{ + if (m_pipeline) + { + if (!m_started) + { + // Send all commands at once + for (std::list <shared_ptr <SMTPCommand> >::const_iterator it = m_commands.begin() ; + it != m_commands.end() ; ++it) + { + shared_ptr <SMTPCommand> cmd = *it; + cmd->writeToSocket(sok); + } + } + + if (!m_commands.empty()) + { + // Advance the pointer to last command sent + shared_ptr <SMTPCommand> cmd = m_commands.front(); + m_commands.pop_front(); + + m_lastCommandSent = cmd; + } + } + else + { + if (!m_commands.empty()) + { + // Send only one command + shared_ptr <SMTPCommand> cmd = m_commands.front(); + m_commands.pop_front(); + + cmd->writeToSocket(sok); + + m_lastCommandSent = cmd; + } + } + + m_started = true; +} + + +const string SMTPCommandSet::getText() const +{ + std::ostringstream cmd; + cmd.imbue(std::locale::classic()); + + for (std::list <shared_ptr <SMTPCommand> >::const_iterator it = m_commands.begin() ; + it != m_commands.end() ; ++it) + { + cmd << (*it)->getText() << "\r\n"; + } + + return cmd.str(); +} + + +bool SMTPCommandSet::isFinished() const +{ + return (m_pipeline && m_started) || (m_commands.size() == 0 && m_started); +} + + +shared_ptr <SMTPCommand> SMTPCommandSet::getLastCommandSent() const +{ + return m_lastCommandSent; +} + + +} // smtp +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP diff --git a/src/vmime/net/smtp/SMTPCommandSet.hpp b/src/vmime/net/smtp/SMTPCommandSet.hpp new file mode 100644 index 00000000..8e744c2b --- /dev/null +++ b/src/vmime/net/smtp/SMTPCommandSet.hpp @@ -0,0 +1,105 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_SMTP_SMTPCOMMANDSET_HPP_INCLUDED +#define VMIME_NET_SMTP_SMTPCOMMANDSET_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + + +#include <list> + +#include "vmime/net/smtp/SMTPCommand.hpp" + + +namespace vmime { +namespace net { +namespace smtp { + + +/** A set of SMTP commands, which may be sent all at once + * to the server if pipelining is supported. + */ +class VMIME_EXPORT SMTPCommandSet : public SMTPCommand +{ +public: + + /** Creates a new set of SMTP commands. + * + * @param pipeline set to true if the server supports pipelining + * @return a new SMTPCommandSet object + */ + static shared_ptr <SMTPCommandSet> create(const bool pipeline); + + /** Adds a new command to this set. + * If one or more comments have already been sent to the server, + * an exception will be thrown. + * + * @param cmd command to add + */ + void addCommand(shared_ptr <SMTPCommand> cmd); + + /** Tests whether all commands have been sent. + * + * @return true if all commands have been sent, + * or false otherwise + */ + bool isFinished() const; + + /** Returns the last command which has been sent. + * + * @return a pointer to a SMTPCommand, of NULL if no command + * has been sent yet + */ + shared_ptr <SMTPCommand> getLastCommandSent() const; + + + void writeToSocket(shared_ptr <socket> sok); + + const string getText() const; + +private: + + SMTPCommandSet(const bool pipeline); + SMTPCommandSet(const SMTPCommandSet&); + + + bool m_pipeline; + bool m_started; + std::list <shared_ptr <SMTPCommand> > m_commands; + shared_ptr <SMTPCommand> m_lastCommandSent; +}; + + +} // smtp +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + +#endif // VMIME_NET_SMTP_SMTPCOMMANDSET_HPP_INCLUDED diff --git a/src/vmime/net/smtp/SMTPConnection.cpp b/src/vmime/net/smtp/SMTPConnection.cpp new file mode 100644 index 00000000..26be25db --- /dev/null +++ b/src/vmime/net/smtp/SMTPConnection.cpp @@ -0,0 +1,618 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + + +#include "vmime/net/smtp/SMTPConnection.hpp" +#include "vmime/net/smtp/SMTPTransport.hpp" +#include "vmime/net/smtp/SMTPExceptions.hpp" + +#include "vmime/exception.hpp" +#include "vmime/platform.hpp" + +#include "vmime/security/digest/messageDigestFactory.hpp" + +#include "vmime/net/defaultConnectionInfos.hpp" + +#if VMIME_HAVE_SASL_SUPPORT + #include "vmime/security/sasl/SASLContext.hpp" +#endif // VMIME_HAVE_SASL_SUPPORT + +#if VMIME_HAVE_TLS_SUPPORT + #include "vmime/net/tls/TLSSession.hpp" + #include "vmime/net/tls/TLSSecuredConnectionInfos.hpp" +#endif // VMIME_HAVE_TLS_SUPPORT + + + +// Helpers for service properties +#define GET_PROPERTY(type, prop) \ + (m_transport.lock()->getInfos().getPropertyValue <type>(getSession(), \ + dynamic_cast <const SMTPServiceInfos&>(m_transport.lock()->getInfos()).getProperties().prop)) +#define HAS_PROPERTY(prop) \ + (m_transport.lock()->getInfos().hasProperty(getSession(), \ + dynamic_cast <const SMTPServiceInfos&>(m_transport.lock()->getInfos()).getProperties().prop)) + + +namespace vmime { +namespace net { +namespace smtp { + + + +SMTPConnection::SMTPConnection(shared_ptr <SMTPTransport> transport, shared_ptr <security::authenticator> auth) + : m_transport(transport), m_auth(auth), m_socket(null), m_timeoutHandler(null), + m_authenticated(false), m_secured(false), m_extendedSMTP(false) +{ +} + + +SMTPConnection::~SMTPConnection() +{ + try + { + if (isConnected()) + disconnect(); + else if (m_socket) + internalDisconnect(); + } + catch (vmime::exception&) + { + // Ignore + } +} + + +void SMTPConnection::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); + + shared_ptr <SMTPTransport> transport = m_transport.lock(); + + // Create the time-out handler + if (transport->getTimeoutHandlerFactory()) + m_timeoutHandler = transport->getTimeoutHandlerFactory()->create(); + + // Create and connect the socket + m_socket = transport->getSocketFactory()->create(m_timeoutHandler); + +#if VMIME_HAVE_TLS_SUPPORT + if (transport->isSMTPS()) // dedicated port/SMTPS + { + shared_ptr <tls::TLSSession> tlsSession = tls::TLSSession::create + (transport->getCertificateVerifier(), + transport->getSession()->getTLSProperties()); + + shared_ptr <tls::TLSSocket> tlsSocket = + tlsSession->getSocket(m_socket); + + m_socket = tlsSocket; + + m_secured = true; + m_cntInfos = make_shared <tls::TLSSecuredConnectionInfos>(address, port, tlsSession, tlsSocket); + } + else +#endif // VMIME_HAVE_TLS_SUPPORT + { + m_cntInfos = make_shared <defaultConnectionInfos>(address, port); + } + + m_socket->connect(address, port); + + // Connection + // + // eg: C: <connection to server> + // --- S: 220 smtp.domain.com Service ready + + shared_ptr <SMTPResponse> resp; + + if ((resp = readResponse())->getCode() != 220) + { + internalDisconnect(); + throw exceptions::connection_greeting_error(resp->getText()); + } + + // Identification + helo(); + +#if VMIME_HAVE_TLS_SUPPORT + // Setup secured connection, if requested + const bool tls = HAS_PROPERTY(PROPERTY_CONNECTION_TLS) + && GET_PROPERTY(bool, PROPERTY_CONNECTION_TLS); + const bool tlsRequired = HAS_PROPERTY(PROPERTY_CONNECTION_TLS_REQUIRED) + && GET_PROPERTY(bool, PROPERTY_CONNECTION_TLS_REQUIRED); + + if (!transport->isSMTPS() && tls) // only if not SMTPS + { + try + { + startTLS(); + } + // Non-fatal error + catch (exceptions::command_error&) + { + if (tlsRequired) + { + throw; + } + else + { + // TLS is not required, so don't bother + } + } + // Fatal error + catch (...) + { + throw; + } + + // Must reissue a EHLO command [RFC-2487, 5.2] + helo(); + } +#endif // VMIME_HAVE_TLS_SUPPORT + + // Authentication + if (GET_PROPERTY(bool, PROPERTY_OPTIONS_NEEDAUTH)) + authenticate(); + else + m_authenticated = true; +} + + +void SMTPConnection::helo() +{ + // First, try Extended SMTP (ESMTP) + // + // eg: C: EHLO thismachine.ourdomain.com + // S: 250-smtp.theserver.com + // S: 250-AUTH CRAM-MD5 DIGEST-MD5 + // S: 250-PIPELINING + // S: 250 SIZE 2555555555 + + sendRequest(SMTPCommand::EHLO(platform::getHandler()->getHostName())); + + shared_ptr <SMTPResponse> resp; + + if ((resp = readResponse())->getCode() != 250) + { + // Next, try "Basic" SMTP + // + // eg: C: HELO thismachine.ourdomain.com + // S: 250 OK + + sendRequest(SMTPCommand::HELO(platform::getHandler()->getHostName())); + + if ((resp = readResponse())->getCode() != 250) + { + internalDisconnect(); + throw exceptions::connection_greeting_error(resp->getLastLine().getText()); + } + + m_extendedSMTP = false; + m_extensions.clear(); + } + else + { + m_extendedSMTP = true; + m_extensions.clear(); + + // Get supported extensions from SMTP response + // One extension per line, format is: EXT PARAM1 PARAM2... + for (size_t i = 1, n = resp->getLineCount() ; i < n ; ++i) + { + const string line = resp->getLineAt(i).getText(); + std::istringstream iss(line); + + string ext; + iss >> ext; + + std::vector <string> params; + string param; + + // Special case: some servers send "AUTH=MECH [MECH MECH...]" + if (ext.length() >= 5 && utility::stringUtils::toUpper(ext.substr(0, 5)) == "AUTH=") + { + params.push_back(utility::stringUtils::toUpper(ext.substr(5))); + ext = "AUTH"; + } + + while (iss >> param) + params.push_back(utility::stringUtils::toUpper(param)); + + m_extensions[ext] = params; + } + } +} + + +bool SMTPConnection::hasExtension + (const std::string& extName, std::vector <string>* params) const +{ + std::map <string, std::vector <string> >::const_iterator + it = m_extensions.find(extName); + + if (it != m_extensions.end()) + { + if (params) + *params = (*it).second; + + return true; + } + else + { + return false; + } +} + + +void SMTPConnection::authenticate() +{ + if (!m_extendedSMTP) + { + internalDisconnect(); + throw exceptions::command_error("AUTH", "ESMTP not supported."); + } + + getAuthenticator()->setService(m_transport.lock()); + +#if VMIME_HAVE_SASL_SUPPORT + // First, try SASL authentication + if (GET_PROPERTY(bool, PROPERTY_OPTIONS_SASL)) + { + try + { + authenticateSASL(); + + m_authenticated = true; + return; + } + catch (exceptions::authentication_error& e) + { + if (!GET_PROPERTY(bool, PROPERTY_OPTIONS_SASL_FALLBACK)) + { + // Can't fallback on normal authentication + internalDisconnect(); + throw e; + } + else + { + // Ignore, will try normal authentication + } + } + catch (exception& e) + { + internalDisconnect(); + throw e; + } + } +#endif // VMIME_HAVE_SASL_SUPPORT + + // No other authentication method is possible + throw exceptions::authentication_error("All authentication methods failed"); +} + + + +#if VMIME_HAVE_SASL_SUPPORT + +void SMTPConnection::authenticateSASL() +{ + if (!dynamicCast <security::sasl::SASLAuthenticator>(getAuthenticator())) + throw exceptions::authentication_error("No SASL authenticator available."); + + // Obtain SASL mechanisms supported by server from ESMTP extensions + std::vector <string> saslMechs; + hasExtension("AUTH", &saslMechs); + + if (saslMechs.empty()) + throw exceptions::authentication_error("No SASL mechanism available."); + + std::vector <shared_ptr <security::sasl::SASLMechanism> > mechList; + + shared_ptr <security::sasl::SASLContext> saslContext = + make_shared <security::sasl::SASLContext>(); + + for (unsigned int i = 0 ; i < saslMechs.size() ; ++i) + { + try + { + mechList.push_back + (saslContext->createMechanism(saslMechs[i])); + } + catch (exceptions::no_such_mechanism&) + { + // Ignore mechanism + } + } + + if (mechList.empty()) + throw exceptions::authentication_error("No SASL mechanism available."); + + // Try to suggest a mechanism among all those supported + shared_ptr <security::sasl::SASLMechanism> suggestedMech = + saslContext->suggestMechanism(mechList); + + if (!suggestedMech) + throw exceptions::authentication_error("Unable to suggest SASL mechanism."); + + // Allow application to choose which mechanisms to use + mechList = dynamicCast <security::sasl::SASLAuthenticator>(getAuthenticator())-> + getAcceptableMechanisms(mechList, suggestedMech); + + if (mechList.empty()) + throw exceptions::authentication_error("No SASL mechanism available."); + + // Try each mechanism in the list in turn + for (unsigned int i = 0 ; i < mechList.size() ; ++i) + { + shared_ptr <security::sasl::SASLMechanism> mech = mechList[i]; + + shared_ptr <security::sasl::SASLSession> saslSession = + saslContext->createSession("smtp", getAuthenticator(), mech); + + saslSession->init(); + + sendRequest(SMTPCommand::AUTH(mech->getName())); + + for (bool cont = true ; cont ; ) + { + shared_ptr <SMTPResponse> response = readResponse(); + + switch (response->getCode()) + { + case 235: + { + m_socket = saslSession->getSecuredSocket(m_socket); + return; + } + case 334: + { + byte_t* challenge = 0; + size_t challengeLen = 0; + + byte_t* resp = 0; + size_t respLen = 0; + + try + { + // Extract challenge + saslContext->decodeB64(response->getText(), &challenge, &challengeLen); + + // Prepare response + saslSession->evaluateChallenge + (challenge, challengeLen, &resp, &respLen); + + // Send response + m_socket->send(saslContext->encodeB64(resp, respLen) + "\r\n"); + } + catch (exceptions::sasl_exception& e) + { + if (challenge) + { + delete [] challenge; + challenge = NULL; + } + + if (resp) + { + delete [] resp; + resp = NULL; + } + + // Cancel SASL exchange + m_socket->send("*\r\n"); + } + catch (...) + { + if (challenge) + delete [] challenge; + + if (resp) + delete [] resp; + + throw; + } + + if (challenge) + delete [] challenge; + + if (resp) + delete [] resp; + + break; + } + default: + + cont = false; + break; + } + } + } + + throw exceptions::authentication_error + ("Could not authenticate using SASL: all mechanisms failed."); +} + +#endif // VMIME_HAVE_SASL_SUPPORT + + +#if VMIME_HAVE_TLS_SUPPORT + +void SMTPConnection::startTLS() +{ + try + { + sendRequest(SMTPCommand::STARTTLS()); + + shared_ptr <SMTPResponse> resp = readResponse(); + + if (resp->getCode() != 220) + { + throw SMTPCommandError("STARTTLS", resp->getText(), + resp->getCode(), resp->getEnhancedCode()); + } + + shared_ptr <tls::TLSSession> tlsSession = tls::TLSSession::create + (getTransport()->getCertificateVerifier(), + getTransport()->getSession()->getTLSProperties()); + + shared_ptr <tls::TLSSocket> tlsSocket = + tlsSession->getSocket(m_socket); + + tlsSocket->handshake(m_timeoutHandler); + + m_socket = tlsSocket; + + m_secured = true; + m_cntInfos = make_shared <tls::TLSSecuredConnectionInfos> + (m_cntInfos->getHost(), m_cntInfos->getPort(), tlsSession, tlsSocket); + } + catch (exceptions::command_error&) + { + // Non-fatal error + throw; + } + catch (exception&) + { + // Fatal error + internalDisconnect(); + throw; + } +} + +#endif // VMIME_HAVE_TLS_SUPPORT + + +void SMTPConnection::disconnect() +{ + if (!isConnected()) + throw exceptions::not_connected(); + + internalDisconnect(); +} + + +void SMTPConnection::internalDisconnect() +{ + try + { + sendRequest(SMTPCommand::QUIT()); + readResponse(); + } + catch (exception&) + { + // Not important + } + + m_socket->disconnect(); + m_socket = null; + + m_timeoutHandler = null; + + m_authenticated = false; + m_extendedSMTP = false; + + m_secured = false; + m_cntInfos = null; +} + + +void SMTPConnection::sendRequest(shared_ptr <SMTPCommand> cmd) +{ + cmd->writeToSocket(m_socket); +} + + +shared_ptr <SMTPResponse> SMTPConnection::readResponse() +{ + shared_ptr <SMTPResponse> resp = SMTPResponse::readResponse + (m_socket, m_timeoutHandler, m_responseState); + + m_responseState = resp->getCurrentState(); + + return resp; +} + + +bool SMTPConnection::isConnected() const +{ + return m_socket && m_socket->isConnected() && m_authenticated; +} + + +bool SMTPConnection::isSecuredConnection() const +{ + return m_secured; +} + + +shared_ptr <connectionInfos> SMTPConnection::getConnectionInfos() const +{ + return m_cntInfos; +} + + +shared_ptr <SMTPTransport> SMTPConnection::getTransport() +{ + return m_transport.lock(); +} + + +shared_ptr <session> SMTPConnection::getSession() +{ + return m_transport.lock()->getSession(); +} + + +shared_ptr <socket> SMTPConnection::getSocket() +{ + return m_socket; +} + + +shared_ptr <timeoutHandler> SMTPConnection::getTimeoutHandler() +{ + return m_timeoutHandler; +} + + +shared_ptr <security::authenticator> SMTPConnection::getAuthenticator() +{ + return m_auth; +} + + +} // smtp +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP diff --git a/src/vmime/net/smtp/SMTPConnection.hpp b/src/vmime/net/smtp/SMTPConnection.hpp new file mode 100644 index 00000000..cc59ef34 --- /dev/null +++ b/src/vmime/net/smtp/SMTPConnection.hpp @@ -0,0 +1,129 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_SMTP_SMTPCONNECTION_HPP_INCLUDED +#define VMIME_NET_SMTP_SMTPCONNECTION_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + + +#include "vmime/messageId.hpp" + +#include "vmime/net/socket.hpp" +#include "vmime/net/timeoutHandler.hpp" +#include "vmime/net/session.hpp" +#include "vmime/net/connectionInfos.hpp" + +#include "vmime/net/smtp/SMTPCommand.hpp" +#include "vmime/net/smtp/SMTPResponse.hpp" + +#include "vmime/security/authenticator.hpp" + + +namespace vmime { +namespace net { + + +class socket; +class timeoutHandler; + + +namespace smtp { + + +class SMTPTransport; + + +/** Manage connection to a SMTP server. + */ +class VMIME_EXPORT SMTPConnection : public object +{ +public: + + SMTPConnection(shared_ptr <SMTPTransport> transport, shared_ptr <security::authenticator> auth); + virtual ~SMTPConnection(); + + + virtual void connect(); + virtual bool isConnected() const; + virtual void disconnect(); + + bool isSecuredConnection() const; + shared_ptr <connectionInfos> getConnectionInfos() const; + + virtual shared_ptr <SMTPTransport> getTransport(); + virtual shared_ptr <socket> getSocket(); + virtual shared_ptr <timeoutHandler> getTimeoutHandler(); + virtual shared_ptr <security::authenticator> getAuthenticator(); + virtual shared_ptr <session> getSession(); + + void sendRequest(shared_ptr <SMTPCommand> cmd); + shared_ptr <SMTPResponse> readResponse(); + + bool hasExtension(const std::string& extName, std::vector <string>* params = NULL) const; + +private: + + void internalDisconnect(); + + void helo(); + void authenticate(); +#if VMIME_HAVE_SASL_SUPPORT + void authenticateSASL(); +#endif // VMIME_HAVE_SASL_SUPPORT + +#if VMIME_HAVE_TLS_SUPPORT + void startTLS(); +#endif // VMIME_HAVE_TLS_SUPPORT + + + weak_ptr <SMTPTransport> m_transport; + + shared_ptr <security::authenticator> m_auth; + shared_ptr <socket> m_socket; + shared_ptr <timeoutHandler> m_timeoutHandler; + + SMTPResponse::state m_responseState; + + bool m_authenticated; + bool m_secured; + + shared_ptr <connectionInfos> m_cntInfos; + + bool m_extendedSMTP; + std::map <string, std::vector <string> > m_extensions; +}; + + +} // smtp +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + +#endif // VMIME_NET_SMTP_SMTPCONNECTION_HPP_INCLUDED diff --git a/src/vmime/net/smtp/SMTPExceptions.cpp b/src/vmime/net/smtp/SMTPExceptions.cpp new file mode 100644 index 00000000..0c3112c0 --- /dev/null +++ b/src/vmime/net/smtp/SMTPExceptions.cpp @@ -0,0 +1,151 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + + +#include "vmime/net/smtp/SMTPExceptions.hpp" + + +namespace vmime { +namespace net { +namespace smtp { + + +// +// SMTPCommandError +// + +SMTPCommandError::SMTPCommandError + (const string& command, const string& response, + const string& desc, const int statusCode, + const SMTPResponse::enhancedStatusCode& extendedStatusCode, + const exception& other) + : command_error(command, response, desc, other), + m_status(statusCode), m_exStatus(extendedStatusCode) +{ +} + + +SMTPCommandError::SMTPCommandError + (const string& command, const string& response, + const int statusCode, const SMTPResponse::enhancedStatusCode& extendedStatusCode, + const exception& other) + : command_error(command, response, "", other), + m_status(statusCode), m_exStatus(extendedStatusCode) +{ +} + + +SMTPCommandError::~SMTPCommandError() throw() +{ +} + + +int SMTPCommandError::statusCode() const +{ + return m_status; +} + + +const SMTPResponse::enhancedStatusCode SMTPCommandError::extendedStatusCode() const +{ + return m_exStatus; +} + + +exception* SMTPCommandError::clone() const +{ + return new SMTPCommandError(*this); +} + + +const char* SMTPCommandError::name() const throw() +{ + return "SMTPCommandError"; +} + + +// +// SMTPMessageSizeExceedsMaxLimitsException +// + +SMTPMessageSizeExceedsMaxLimitsException::SMTPMessageSizeExceedsMaxLimitsException(const exception& other) + : net_exception("Message size exceeds maximum server limits (permanent error).", other) +{ +} + + +SMTPMessageSizeExceedsMaxLimitsException::~SMTPMessageSizeExceedsMaxLimitsException() throw() +{ +} + + +exception* SMTPMessageSizeExceedsMaxLimitsException::clone() const +{ + return new SMTPMessageSizeExceedsMaxLimitsException(*this); +} + + +const char* SMTPMessageSizeExceedsMaxLimitsException::name() const throw() +{ + return "SMTPMessageSizeExceedsMaxLimitsException"; +} + + +// +// SMTPMessageSizeExceedsCurLimitsException +// + +SMTPMessageSizeExceedsCurLimitsException::SMTPMessageSizeExceedsCurLimitsException(const exception& other) + : net_exception("Message size exceeds current server limits (temporary storage error).", other) +{ +} + + +SMTPMessageSizeExceedsCurLimitsException::~SMTPMessageSizeExceedsCurLimitsException() throw() +{ +} + + +exception* SMTPMessageSizeExceedsCurLimitsException::clone() const +{ + return new SMTPMessageSizeExceedsCurLimitsException(*this); +} + + +const char* SMTPMessageSizeExceedsCurLimitsException::name() const throw() +{ + return "SMTPMessageSizeExceedsCurLimitsException"; +} + + +} // smtp +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP diff --git a/src/vmime/net/smtp/SMTPExceptions.hpp b/src/vmime/net/smtp/SMTPExceptions.hpp new file mode 100644 index 00000000..75842042 --- /dev/null +++ b/src/vmime/net/smtp/SMTPExceptions.hpp @@ -0,0 +1,127 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_SMTP_SMTPEXCEPTIONS_HPP_INCLUDED +#define VMIME_NET_SMTP_SMTPEXCEPTIONS_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + + +#include "vmime/exception.hpp" +#include "vmime/base.hpp" + +#include "vmime/net/smtp/SMTPResponse.hpp" + + +namespace vmime { +namespace net { +namespace smtp { + + +/** SMTP Command error: a SMTP command failed. + */ + +class VMIME_EXPORT SMTPCommandError : public exceptions::command_error +{ +public: + + SMTPCommandError(const string& command, const string& response, + const string& desc, const int statusCode, + const SMTPResponse::enhancedStatusCode& extendedStatusCode, + const exception& other = NO_EXCEPTION); + + SMTPCommandError(const string& command, const string& response, + const int statusCode, const SMTPResponse::enhancedStatusCode& extendedStatusCode, + const exception& other = NO_EXCEPTION); + + ~SMTPCommandError() throw(); + + /** Returns the SMTP status code for this error. + * + * @return status code (protocol-dependent) + */ + int statusCode() const; + + /** Returns the extended status code (following RFC-3463) for this + * error, if available. + * + * @return status code + */ + const SMTPResponse::enhancedStatusCode extendedStatusCode() const; + + + exception* clone() const; + const char* name() const throw(); + +private: + + int m_status; + SMTPResponse::enhancedStatusCode m_exStatus; +}; + + +/** SMTP error: message size exceeds maximum server limits. + * This is a permanent error. + */ + +class VMIME_EXPORT SMTPMessageSizeExceedsMaxLimitsException : public exceptions::net_exception +{ +public: + + SMTPMessageSizeExceedsMaxLimitsException(const exception& other = NO_EXCEPTION); + ~SMTPMessageSizeExceedsMaxLimitsException() throw(); + + exception* clone() const; + const char* name() const throw(); +}; + + +/** SMTP error: message size exceeds current server limits. + * This is a temporary error (you may retry later). + */ + +class VMIME_EXPORT SMTPMessageSizeExceedsCurLimitsException : public exceptions::net_exception +{ +public: + + SMTPMessageSizeExceedsCurLimitsException(const exception& other = NO_EXCEPTION); + ~SMTPMessageSizeExceedsCurLimitsException() throw(); + + exception* clone() const; + const char* name() const throw(); +}; + + +} // smtp +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + +#endif // VMIME_NET_SMTP_SMTPEXCEPTIONS_HPP_INCLUDED + diff --git a/src/vmime/net/smtp/SMTPResponse.cpp b/src/vmime/net/smtp/SMTPResponse.cpp new file mode 100644 index 00000000..f7980351 --- /dev/null +++ b/src/vmime/net/smtp/SMTPResponse.cpp @@ -0,0 +1,334 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + + +#include "vmime/net/smtp/SMTPResponse.hpp" + +#include "vmime/platform.hpp" +#include "vmime/utility/stringUtils.hpp" + +#include "vmime/net/socket.hpp" +#include "vmime/net/timeoutHandler.hpp" + +#include <cctype> + + +namespace vmime { +namespace net { +namespace smtp { + + +SMTPResponse::SMTPResponse(shared_ptr <socket> sok, shared_ptr <timeoutHandler> toh, const state& st) + : m_socket(sok), m_timeoutHandler(toh), + m_responseBuffer(st.responseBuffer), m_responseContinues(false) +{ +} + + +SMTPResponse::SMTPResponse(const SMTPResponse&) + : vmime::object() +{ + // Not used +} + + +int SMTPResponse::getCode() const +{ + const int firstCode = m_lines[0].getCode(); + + for (unsigned int i = 1 ; i < m_lines.size() ; ++i) + { + // All response codes returned must be equal + // or else this in an error... + if (m_lines[i].getCode() != firstCode) + return 0; + } + + return firstCode; +} + + +const SMTPResponse::enhancedStatusCode SMTPResponse::getEnhancedCode() const +{ + return m_lines[m_lines.size() - 1].getEnhancedCode(); +} + + +const string SMTPResponse::getText() const +{ + string text = m_lines[0].getText(); + + for (unsigned int i = 1 ; i < m_lines.size() ; ++i) + { + text += '\n'; + text += m_lines[i].getText(); + } + + return text; +} + + +// static +shared_ptr <SMTPResponse> SMTPResponse::readResponse + (shared_ptr <socket> sok, shared_ptr <timeoutHandler> toh, const state& st) +{ + shared_ptr <SMTPResponse> resp = shared_ptr <SMTPResponse>(new SMTPResponse(sok, toh, st)); + + resp->readResponse(); + + return resp; +} + + +void SMTPResponse::readResponse() +{ + responseLine line = getNextResponse(); + m_lines.push_back(line); + + while (m_responseContinues) + { + line = getNextResponse(); + m_lines.push_back(line); + } +} + + +const string SMTPResponse::readResponseLine() +{ + string currentBuffer = m_responseBuffer; + + if (m_timeoutHandler) + m_timeoutHandler->resetTimeOut(); + + while (true) + { + // Get a line from the response buffer + const size_t lineEnd = currentBuffer.find_first_of('\n'); + + if (lineEnd != string::npos) + { + size_t actualLineEnd = lineEnd; + + if (actualLineEnd != 0 && currentBuffer[actualLineEnd - 1] == '\r') // CRLF case + actualLineEnd--; + + const string line(currentBuffer.begin(), currentBuffer.begin() + actualLineEnd); + + currentBuffer.erase(currentBuffer.begin(), currentBuffer.begin() + lineEnd + 1); + m_responseBuffer = currentBuffer; + + return line; + } + + // Check whether the time-out delay is elapsed + if (m_timeoutHandler && m_timeoutHandler->isTimeOut()) + { + if (!m_timeoutHandler->handleTimeOut()) + throw exceptions::operation_timed_out(); + + m_timeoutHandler->resetTimeOut(); + } + + // Receive data from the socket + string receiveBuffer; + m_socket->receive(receiveBuffer); + + if (receiveBuffer.empty()) // buffer is empty + { + platform::getHandler()->wait(); + continue; + } + + currentBuffer += receiveBuffer; + } +} + + +const SMTPResponse::responseLine SMTPResponse::getNextResponse() +{ + string line = readResponseLine(); + + const int code = extractResponseCode(line); + string text; + + m_responseContinues = (line.length() >= 4 && line[3] == '-'); + + if (line.length() > 4) + text = utility::stringUtils::trim(line.substr(4)); + else + text = ""; + + return responseLine(code, text, extractEnhancedCode(text)); +} + + +// static +int SMTPResponse::extractResponseCode(const string& response) +{ + int code = 0; + + if (response.length() >= 3) + { + code = (response[0] - '0') * 100 + + (response[1] - '0') * 10 + + (response[2] - '0'); + } + + return code; +} + + +// static +const SMTPResponse::enhancedStatusCode SMTPResponse::extractEnhancedCode(const string& responseText) +{ + enhancedStatusCode enhCode; + + std::istringstream iss(responseText); + + if (std::isdigit(iss.peek())) + { + iss >> enhCode.klass; + + if (iss.get() == '.' && std::isdigit(iss.peek())) + { + iss >> enhCode.subject; + + if (iss.get() == '.' && std::isdigit(iss.peek())) + { + iss >> enhCode.detail; + return enhCode; + } + } + } + + return enhancedStatusCode(); // no enhanced code found +} + + +const SMTPResponse::responseLine SMTPResponse::getLineAt(const size_t pos) const +{ + return m_lines[pos]; +} + + +size_t SMTPResponse::getLineCount() const +{ + return m_lines.size(); +} + + +const SMTPResponse::responseLine SMTPResponse::getLastLine() const +{ + return m_lines[m_lines.size() - 1]; +} + + +const SMTPResponse::state SMTPResponse::getCurrentState() const +{ + state st; + st.responseBuffer = m_responseBuffer; + + return st; +} + + + +// SMTPResponse::responseLine + +SMTPResponse::responseLine::responseLine(const int code, const string& text, const enhancedStatusCode& enhCode) + : m_code(code), m_text(text), m_enhCode(enhCode) +{ +} + + +void SMTPResponse::responseLine::setCode(const int code) +{ + m_code = code; +} + + +int SMTPResponse::responseLine::getCode() const +{ + return m_code; +} + + +void SMTPResponse::responseLine::setEnhancedCode(const enhancedStatusCode& enhCode) +{ + m_enhCode = enhCode; +} + + +const SMTPResponse::enhancedStatusCode SMTPResponse::responseLine::getEnhancedCode() const +{ + return m_enhCode; +} + + +void SMTPResponse::responseLine::setText(const string& text) +{ + m_text = text; +} + + +const string SMTPResponse::responseLine::getText() const +{ + return m_text; +} + + + +// SMTPResponse::enhancedStatusCode + + +SMTPResponse::enhancedStatusCode::enhancedStatusCode() + : klass(0), subject(0), detail(0) +{ +} + + +SMTPResponse::enhancedStatusCode::enhancedStatusCode(const enhancedStatusCode& enhCode) + : klass(enhCode.klass), subject(enhCode.subject), detail(enhCode.detail) +{ +} + + +std::ostream& operator<<(std::ostream& os, const SMTPResponse::enhancedStatusCode& code) +{ + os << code.klass << '.' << code.subject << '.' << code.detail; + return os; +} + + +} // smtp +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + diff --git a/src/vmime/net/smtp/SMTPResponse.hpp b/src/vmime/net/smtp/SMTPResponse.hpp new file mode 100644 index 00000000..000448ac --- /dev/null +++ b/src/vmime/net/smtp/SMTPResponse.hpp @@ -0,0 +1,186 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_SMTP_SMTPRESPONSE_HPP_INCLUDED +#define VMIME_NET_SMTP_SMTPRESPONSE_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + + +#include "vmime/object.hpp" +#include "vmime/base.hpp" + + +namespace vmime { +namespace net { + + +class socket; +class timeoutHandler; + + +namespace smtp { + + +/** A SMTP response, as sent by the server. + */ +class VMIME_EXPORT SMTPResponse : public object +{ +public: + + /** Current state of response parser. */ + struct state + { + string responseBuffer; + }; + + /** Enhanced status code (as per RFC-3463). */ + struct enhancedStatusCode + { + enhancedStatusCode(); + enhancedStatusCode(const enhancedStatusCode& enhCode); + + unsigned short klass; /**< Success/failure. */ + unsigned short subject; /**< Source of anomaly. */ + unsigned short detail; /**< Precise error condition. */ + }; + + /** An element of a SMTP response. */ + class responseLine + { + public: + + responseLine(const int code, const string& text, const enhancedStatusCode& enhCode); + + void setCode(const int code); + int getCode() const; + + void setEnhancedCode(const enhancedStatusCode& enhCode); + const enhancedStatusCode getEnhancedCode() const; + + void setText(const string& text); + const string getText() const; + + private: + + int m_code; + string m_text; + enhancedStatusCode m_enhCode; + }; + + /** Receive and parse a new SMTP response from the + * specified socket. + * + * @param sok socket from which to read + * @param toh time-out handler + * @param st previous state of response parser for the specified socket + * @return SMTP response + * @throws exceptions::operation_timed_out if no data + * has been received within the granted time + */ + static shared_ptr <SMTPResponse> readResponse(shared_ptr <socket> sok, shared_ptr <timeoutHandler> toh, const state& st); + + /** Return the SMTP response code. + * + * @return response code + */ + int getCode() const; + + /** Return the SMTP enhanced status code, if available. + * + * @return enhanced status code + */ + const enhancedStatusCode getEnhancedCode() const; + + /** Return the SMTP response text. + * The text of each line is concatenated. + * + * @return response text + */ + const string getText() const; + + /** Return the response line at the specified position. + * + * @param pos line index + * @return line at the specified index + */ + const responseLine getLineAt(const size_t pos) const; + + /** Return the number of lines in the response. + * + * @return number of lines in the response + */ + size_t getLineCount() const; + + /** Return the last line in the response. + * + * @return last response line + */ + const responseLine getLastLine() const; + + /** Returns the current state of the response parser. + * + * @return current parser state + */ + const state getCurrentState() const; + +private: + + SMTPResponse(shared_ptr <socket> sok, shared_ptr <timeoutHandler> toh, const state& st); + SMTPResponse(const SMTPResponse&); + + void readResponse(); + + const string readResponseLine(); + const responseLine getNextResponse(); + + static int extractResponseCode(const string& response); + static const enhancedStatusCode extractEnhancedCode(const string& responseText); + + + std::vector <responseLine> m_lines; + + shared_ptr <socket> m_socket; + shared_ptr <timeoutHandler> m_timeoutHandler; + + string m_responseBuffer; + bool m_responseContinues; +}; + + +VMIME_EXPORT std::ostream& operator<<(std::ostream& os, const SMTPResponse::enhancedStatusCode& code); + + +} // smtp +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + +#endif // VMIME_NET_SMTP_SMTPRESPONSE_HPP_INCLUDED + diff --git a/src/vmime/net/smtp/SMTPSTransport.cpp b/src/vmime/net/smtp/SMTPSTransport.cpp new file mode 100644 index 00000000..ab64d49d --- /dev/null +++ b/src/vmime/net/smtp/SMTPSTransport.cpp @@ -0,0 +1,79 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + + +#include "vmime/net/smtp/SMTPSTransport.hpp" + + +namespace vmime { +namespace net { +namespace smtp { + + +SMTPSTransport::SMTPSTransport(shared_ptr <session> sess, shared_ptr <security::authenticator> auth) + : SMTPTransport(sess, auth, true) +{ +} + + +SMTPSTransport::~SMTPSTransport() +{ +} + + +const string SMTPSTransport::getProtocolName() const +{ + return "smtps"; +} + + + +// Service infos + +SMTPServiceInfos SMTPSTransport::sm_infos(true); + + +const serviceInfos& SMTPSTransport::getInfosInstance() +{ + return sm_infos; +} + + +const serviceInfos& SMTPSTransport::getInfos() const +{ + return sm_infos; +} + + +} // smtp +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + diff --git a/src/vmime/net/smtp/SMTPSTransport.hpp b/src/vmime/net/smtp/SMTPSTransport.hpp new file mode 100644 index 00000000..7782f711 --- /dev/null +++ b/src/vmime/net/smtp/SMTPSTransport.hpp @@ -0,0 +1,71 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_SMTP_SMTPSSTORE_HPP_INCLUDED +#define VMIME_NET_SMTP_SMTPSSTORE_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + + +#include "vmime/net/smtp/SMTPTransport.hpp" + + +namespace vmime { +namespace net { +namespace smtp { + + +/** SMTPS transport service. + */ + +class VMIME_EXPORT SMTPSTransport : public SMTPTransport +{ +public: + + SMTPSTransport(shared_ptr <session> sess, shared_ptr <security::authenticator> auth); + ~SMTPSTransport(); + + const string getProtocolName() const; + + static const serviceInfos& getInfosInstance(); + const serviceInfos& getInfos() const; + +private: + + static SMTPServiceInfos sm_infos; +}; + + +} // smtp +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + +#endif // VMIME_NET_SMTP_SMTPSSTORE_HPP_INCLUDED + diff --git a/src/vmime/net/smtp/SMTPServiceInfos.cpp b/src/vmime/net/smtp/SMTPServiceInfos.cpp new file mode 100644 index 00000000..532bb8b8 --- /dev/null +++ b/src/vmime/net/smtp/SMTPServiceInfos.cpp @@ -0,0 +1,146 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + + +#include "vmime/net/smtp/SMTPServiceInfos.hpp" + + +namespace vmime { +namespace net { +namespace smtp { + + +SMTPServiceInfos::SMTPServiceInfos(const bool smtps) + : m_smtps(smtps) +{ +} + + +const string SMTPServiceInfos::getPropertyPrefix() const +{ + if (m_smtps) + return "transport.smtps."; + else + return "transport.smtp."; +} + + +const SMTPServiceInfos::props& SMTPServiceInfos::getProperties() const +{ + static props smtpProps = + { + // SMTP-specific options + property("options.need-authentication", serviceInfos::property::TYPE_BOOLEAN, "false"), +#if VMIME_HAVE_SASL_SUPPORT + property("options.sasl", serviceInfos::property::TYPE_BOOLEAN, "true"), + property("options.sasl.fallback", serviceInfos::property::TYPE_BOOLEAN, "false"), +#endif // VMIME_HAVE_SASL_SUPPORT + + property("options.pipelining", serviceInfos::property::TYPE_BOOLEAN, "true"), + property("options.chunking", serviceInfos::property::TYPE_BOOLEAN, "true"), + + // Common properties + property(serviceInfos::property::AUTH_USERNAME, serviceInfos::property::FLAG_REQUIRED), + property(serviceInfos::property::AUTH_PASSWORD, serviceInfos::property::FLAG_REQUIRED), + +#if VMIME_HAVE_TLS_SUPPORT + property(serviceInfos::property::CONNECTION_TLS), + property(serviceInfos::property::CONNECTION_TLS_REQUIRED), +#endif // VMIME_HAVE_TLS_SUPPORT + + property(serviceInfos::property::SERVER_ADDRESS, serviceInfos::property::FLAG_REQUIRED), + property(serviceInfos::property::SERVER_PORT, "25"), + }; + + static props smtpsProps = + { + // SMTP-specific options + property("options.need-authentication", serviceInfos::property::TYPE_BOOLEAN, "false"), +#if VMIME_HAVE_SASL_SUPPORT + property("options.sasl", serviceInfos::property::TYPE_BOOLEAN, "true"), + property("options.sasl.fallback", serviceInfos::property::TYPE_BOOLEAN, "false"), +#endif // VMIME_HAVE_SASL_SUPPORT + + property("options.pipelining", serviceInfos::property::TYPE_BOOLEAN, "true"), + property("options.chunking", serviceInfos::property::TYPE_BOOLEAN, "true"), + + // Common properties + property(serviceInfos::property::AUTH_USERNAME, serviceInfos::property::FLAG_REQUIRED), + property(serviceInfos::property::AUTH_PASSWORD, serviceInfos::property::FLAG_REQUIRED), + +#if VMIME_HAVE_TLS_SUPPORT + property(serviceInfos::property::CONNECTION_TLS), + property(serviceInfos::property::CONNECTION_TLS_REQUIRED), +#endif // VMIME_HAVE_TLS_SUPPORT + + property(serviceInfos::property::SERVER_ADDRESS, serviceInfos::property::FLAG_REQUIRED), + property(serviceInfos::property::SERVER_PORT, "465"), + }; + + return m_smtps ? smtpsProps : smtpProps; +} + + +const std::vector <serviceInfos::property> SMTPServiceInfos::getAvailableProperties() const +{ + std::vector <property> list; + const props& p = getProperties(); + + // SMTP-specific options + list.push_back(p.PROPERTY_OPTIONS_NEEDAUTH); +#if VMIME_HAVE_SASL_SUPPORT + list.push_back(p.PROPERTY_OPTIONS_SASL); + list.push_back(p.PROPERTY_OPTIONS_SASL_FALLBACK); +#endif // VMIME_HAVE_SASL_SUPPORT + + // Common properties + list.push_back(p.PROPERTY_AUTH_USERNAME); + list.push_back(p.PROPERTY_AUTH_PASSWORD); + +#if VMIME_HAVE_TLS_SUPPORT + if (!m_smtps) + { + list.push_back(p.PROPERTY_CONNECTION_TLS); + list.push_back(p.PROPERTY_CONNECTION_TLS_REQUIRED); + } +#endif // VMIME_HAVE_TLS_SUPPORT + + list.push_back(p.PROPERTY_SERVER_ADDRESS); + list.push_back(p.PROPERTY_SERVER_PORT); + + return list; +} + + +} // smtp +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + diff --git a/src/vmime/net/smtp/SMTPServiceInfos.hpp b/src/vmime/net/smtp/SMTPServiceInfos.hpp new file mode 100644 index 00000000..f783194d --- /dev/null +++ b/src/vmime/net/smtp/SMTPServiceInfos.hpp @@ -0,0 +1,95 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_SMTP_SMTPSERVICEINFOS_HPP_INCLUDED +#define VMIME_NET_SMTP_SMTPSERVICEINFOS_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + + +#include "vmime/net/serviceInfos.hpp" + + +namespace vmime { +namespace net { +namespace smtp { + + +/** Information about SMTP service. + */ + +class VMIME_EXPORT SMTPServiceInfos : public serviceInfos +{ +public: + + SMTPServiceInfos(const bool smtps); + + struct props + { + // SMTP-specific options + serviceInfos::property PROPERTY_OPTIONS_NEEDAUTH; +#if VMIME_HAVE_SASL_SUPPORT + serviceInfos::property PROPERTY_OPTIONS_SASL; + serviceInfos::property PROPERTY_OPTIONS_SASL_FALLBACK; +#endif // VMIME_HAVE_SASL_SUPPORT + + serviceInfos::property PROPERTY_OPTIONS_PIPELINING; + serviceInfos::property PROPERTY_OPTIONS_CHUNKING; + + // Common properties + serviceInfos::property PROPERTY_AUTH_USERNAME; + serviceInfos::property PROPERTY_AUTH_PASSWORD; + +#if VMIME_HAVE_TLS_SUPPORT + serviceInfos::property PROPERTY_CONNECTION_TLS; + serviceInfos::property PROPERTY_CONNECTION_TLS_REQUIRED; +#endif // VMIME_HAVE_TLS_SUPPORT + + serviceInfos::property PROPERTY_SERVER_ADDRESS; + serviceInfos::property PROPERTY_SERVER_PORT; + }; + + const props& getProperties() const; + + const string getPropertyPrefix() const; + const std::vector <serviceInfos::property> getAvailableProperties() const; + +private: + + const bool m_smtps; +}; + + +} // smtp +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + +#endif // VMIME_NET_SMTP_SMTPSERVICEINFOS_HPP_INCLUDED + diff --git a/src/vmime/net/smtp/SMTPTransport.cpp b/src/vmime/net/smtp/SMTPTransport.cpp new file mode 100644 index 00000000..0020d010 --- /dev/null +++ b/src/vmime/net/smtp/SMTPTransport.cpp @@ -0,0 +1,420 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + + +#include "vmime/net/smtp/SMTPTransport.hpp" +#include "vmime/net/smtp/SMTPResponse.hpp" +#include "vmime/net/smtp/SMTPCommand.hpp" +#include "vmime/net/smtp/SMTPCommandSet.hpp" +#include "vmime/net/smtp/SMTPChunkingOutputStreamAdapter.hpp" +#include "vmime/net/smtp/SMTPExceptions.hpp" + +#include "vmime/exception.hpp" +#include "vmime/mailboxList.hpp" +#include "vmime/message.hpp" + +#include "vmime/utility/filteredStream.hpp" +#include "vmime/utility/stringUtils.hpp" +#include "vmime/utility/outputStreamSocketAdapter.hpp" +#include "vmime/utility/streamUtils.hpp" +#include "vmime/utility/outputStreamAdapter.hpp" +#include "vmime/utility/inputStreamStringAdapter.hpp" + + +namespace vmime { +namespace net { +namespace smtp { + + +SMTPTransport::SMTPTransport(shared_ptr <session> sess, shared_ptr <security::authenticator> auth, const bool secured) + : transport(sess, getInfosInstance(), auth), m_isSMTPS(secured), m_needReset(false) +{ +} + + +SMTPTransport::~SMTPTransport() +{ + try + { + if (isConnected()) + disconnect(); + } + catch (vmime::exception&) + { + // Ignore + } +} + + +const string SMTPTransport::getProtocolName() const +{ + return "smtp"; +} + + +bool SMTPTransport::isSMTPS() const +{ + return m_isSMTPS; +} + + +void SMTPTransport::connect() +{ + if (isConnected()) + throw exceptions::already_connected(); + + m_connection = make_shared <SMTPConnection> + (dynamicCast <SMTPTransport>(shared_from_this()), getAuthenticator()); + + try + { + m_connection->connect(); + } + catch (std::exception&) + { + m_connection = null; + throw; + } +} + + +bool SMTPTransport::isConnected() const +{ + return m_connection && m_connection->isConnected(); +} + + +bool SMTPTransport::isSecuredConnection() const +{ + if (m_connection == NULL) + return false; + + return m_connection->isSecuredConnection(); +} + + +shared_ptr <connectionInfos> SMTPTransport::getConnectionInfos() const +{ + if (m_connection == NULL) + return null; + + return m_connection->getConnectionInfos(); +} + + +shared_ptr <SMTPConnection> SMTPTransport::getConnection() +{ + return m_connection; +} + + +void SMTPTransport::disconnect() +{ + if (!isConnected()) + throw exceptions::not_connected(); + + m_connection->disconnect(); + m_connection = null; +} + + +void SMTPTransport::noop() +{ + if (!isConnected()) + throw exceptions::not_connected(); + + m_connection->sendRequest(SMTPCommand::NOOP()); + + shared_ptr <SMTPResponse> resp = m_connection->readResponse(); + + if (resp->getCode() != 250) + { + throw SMTPCommandError + ("NOOP", resp->getText(), resp->getCode(), resp->getEnhancedCode()); + } +} + + +void SMTPTransport::sendEnvelope + (const mailbox& expeditor, const mailboxList& recipients, + const mailbox& sender, bool sendDATACommand, + const size_t size) +{ + // If no recipient/expeditor was found, throw an exception + if (recipients.isEmpty()) + throw exceptions::no_recipient(); + else if (expeditor.isEmpty()) + throw exceptions::no_expeditor(); + + + const bool needReset = m_needReset; + const bool hasPipelining = m_connection->hasExtension("PIPELINING") && + getInfos().getPropertyValue <bool>(getSession(), + dynamic_cast <const SMTPServiceInfos&>(getInfos()).getProperties().PROPERTY_OPTIONS_PIPELINING); + + shared_ptr <SMTPResponse> resp; + shared_ptr <SMTPCommandSet> commands = SMTPCommandSet::create(hasPipelining); + + // Emit a "RSET" command if we previously sent a message on this connection + if (needReset) + commands->addCommand(SMTPCommand::RSET()); + + // Emit the "MAIL" command + const bool hasSMTPUTF8 = m_connection->hasExtension("SMTPUTF8"); + const bool hasSize = m_connection->hasExtension("SIZE"); + + if (!sender.isEmpty()) + commands->addCommand(SMTPCommand::MAIL(sender, hasSMTPUTF8, hasSize ? size : 0)); + else + commands->addCommand(SMTPCommand::MAIL(expeditor, hasSMTPUTF8, hasSize ? size : 0)); + + // Now, we will need to reset next time + m_needReset = true; + + // Emit a "RCPT TO" command for each recipient + for (size_t i = 0 ; i < recipients.getMailboxCount() ; ++i) + { + const mailbox& mbox = *recipients.getMailboxAt(i); + commands->addCommand(SMTPCommand::RCPT(mbox, hasSMTPUTF8)); + } + + // Prepare sending of message data + if (sendDATACommand) + commands->addCommand(SMTPCommand::DATA()); + + // Read response for "RSET" command + if (needReset) + { + commands->writeToSocket(m_connection->getSocket()); + + if ((resp = m_connection->readResponse())->getCode() != 250) + { + disconnect(); + + throw SMTPCommandError + (commands->getLastCommandSent()->getText(), resp->getText(), + resp->getCode(), resp->getEnhancedCode()); + } + } + + // Read response for "MAIL" command + commands->writeToSocket(m_connection->getSocket()); + + if ((resp = m_connection->readResponse())->getCode() != 250) + { + // SIZE extension: insufficient system storage + if (resp->getCode() == 452) + { + disconnect(); + + throw SMTPMessageSizeExceedsCurLimitsException + (SMTPCommandError(commands->getLastCommandSent()->getText(), resp->getText(), + resp->getCode(), resp->getEnhancedCode())); + } + // SIZE extension: message size exceeds fixed maximum message size + else if (resp->getCode() == 552) + { + disconnect(); + + throw SMTPMessageSizeExceedsMaxLimitsException + (SMTPCommandError(commands->getLastCommandSent()->getText(), resp->getText(), + resp->getCode(), resp->getEnhancedCode())); + } + // Other error + else + { + disconnect(); + + throw SMTPCommandError + (commands->getLastCommandSent()->getText(), resp->getText(), + resp->getCode(), resp->getEnhancedCode()); + } + } + + // Read responses for "RCPT TO" commands + for (size_t i = 0 ; i < recipients.getMailboxCount() ; ++i) + { + commands->writeToSocket(m_connection->getSocket()); + + resp = m_connection->readResponse(); + + if (resp->getCode() != 250 && + resp->getCode() != 251) + { + // SIZE extension: insufficient system storage + if (resp->getCode() == 452) + { + disconnect(); + + throw SMTPMessageSizeExceedsCurLimitsException + (SMTPCommandError(commands->getLastCommandSent()->getText(), resp->getText(), + resp->getCode(), resp->getEnhancedCode())); + } + // SIZE extension: message size exceeds fixed maximum message size + else if (resp->getCode() == 552) + { + disconnect(); + + throw SMTPMessageSizeExceedsMaxLimitsException + (SMTPCommandError(commands->getLastCommandSent()->getText(), resp->getText(), + resp->getCode(), resp->getEnhancedCode())); + } + // Other error + else + { + disconnect(); + + throw SMTPCommandError + (commands->getLastCommandSent()->getText(), resp->getText(), + resp->getCode(), resp->getEnhancedCode()); + } + } + } + + // Read response for "DATA" command + if (sendDATACommand) + { + commands->writeToSocket(m_connection->getSocket()); + + if ((resp = m_connection->readResponse())->getCode() != 354) + { + disconnect(); + + throw SMTPCommandError + (commands->getLastCommandSent()->getText(), resp->getText(), + resp->getCode(), resp->getEnhancedCode()); + } + } +} + + +void SMTPTransport::send + (const mailbox& expeditor, const mailboxList& recipients, + utility::inputStream& is, const size_t size, + utility::progressListener* progress, const mailbox& sender) +{ + if (!isConnected()) + throw exceptions::not_connected(); + + // Send message envelope + sendEnvelope(expeditor, recipients, sender, /* sendDATACommand */ true, size); + + // Send the message data + // Stream copy with "\n." to "\n.." transformation + utility::outputStreamSocketAdapter sos(*m_connection->getSocket()); + utility::dotFilteredOutputStream fos(sos); + + utility::bufferedStreamCopy(is, fos, size, progress); + + fos.flush(); + + // Send end-of-data delimiter + m_connection->getSocket()->send("\r\n.\r\n"); + + shared_ptr <SMTPResponse> resp; + + if ((resp = m_connection->readResponse())->getCode() != 250) + { + disconnect(); + + throw SMTPCommandError + ("DATA", resp->getText(), resp->getCode(), resp->getEnhancedCode()); + } +} + + +void SMTPTransport::send + (shared_ptr <vmime::message> msg, const mailbox& expeditor, const mailboxList& recipients, + utility::progressListener* progress, const mailbox& sender) +{ + if (!isConnected()) + throw exceptions::not_connected(); + + // Generate the message with Internationalized Email support, + // if this is supported by the SMTP server + generationContext ctx(generationContext::getDefaultContext()); + ctx.setInternationalizedEmailSupport(m_connection->hasExtension("SMTPUTF8")); + + // If CHUNKING is not supported, generate the message to a temporary + // buffer then use the send() method which takes an inputStream + if (!m_connection->hasExtension("CHUNKING") || + !getInfos().getPropertyValue <bool>(getSession(), + dynamic_cast <const SMTPServiceInfos&>(getInfos()).getProperties().PROPERTY_OPTIONS_CHUNKING)) + + { + std::ostringstream oss; + utility::outputStreamAdapter ossAdapter(oss); + + msg->generate(ctx, ossAdapter); + + const string& str(oss.str()); + + utility::inputStreamStringAdapter isAdapter(str); + + send(expeditor, recipients, isAdapter, str.length(), progress, sender); + return; + } + + // Send message envelope + sendEnvelope(expeditor, recipients, sender, + /* sendDATACommand */ false, msg->getGeneratedSize(ctx)); + + // Send the message by chunks + SMTPChunkingOutputStreamAdapter chunkStream(m_connection); + + msg->generate(ctx, chunkStream); + + chunkStream.flush(); +} + + + +// Service infos + +SMTPServiceInfos SMTPTransport::sm_infos(false); + + +const serviceInfos& SMTPTransport::getInfosInstance() +{ + return sm_infos; +} + + +const serviceInfos& SMTPTransport::getInfos() const +{ + return sm_infos; +} + + +} // smtp +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + diff --git a/src/vmime/net/smtp/SMTPTransport.hpp b/src/vmime/net/smtp/SMTPTransport.hpp new file mode 100644 index 00000000..a0f02418 --- /dev/null +++ b/src/vmime/net/smtp/SMTPTransport.hpp @@ -0,0 +1,131 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_SMTP_SMTPTRANSPORT_HPP_INCLUDED +#define VMIME_NET_SMTP_SMTPTRANSPORT_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + + +#include "vmime/net/transport.hpp" +#include "vmime/net/socket.hpp" +#include "vmime/net/timeoutHandler.hpp" + +#include "vmime/net/smtp/SMTPServiceInfos.hpp" +#include "vmime/net/smtp/SMTPConnection.hpp" + + +namespace vmime { +namespace net { +namespace smtp { + + +class SMTPCommand; + + +/** SMTP transport service. + */ + +class VMIME_EXPORT SMTPTransport : public transport +{ +public: + + SMTPTransport(shared_ptr <session> sess, shared_ptr <security::authenticator> auth, const bool secured = false); + ~SMTPTransport(); + + const string getProtocolName() const; + + static const serviceInfos& getInfosInstance(); + const serviceInfos& getInfos() const; + + void connect(); + bool isConnected() const; + void disconnect(); + + void noop(); + + void send + (const mailbox& expeditor, + const mailboxList& recipients, + utility::inputStream& is, + const size_t size, + utility::progressListener* progress = NULL, + const mailbox& sender = mailbox()); + + void send + (shared_ptr <vmime::message> msg, + const mailbox& expeditor, + const mailboxList& recipients, + utility::progressListener* progress = NULL, + const mailbox& sender = mailbox()); + + bool isSecuredConnection() const; + shared_ptr <connectionInfos> getConnectionInfos() const; + shared_ptr <SMTPConnection> getConnection(); + + bool isSMTPS() const; + +private: + + /** Send the MAIL and RCPT commands to the server, checking the + * response, and using pipelining if supported by the server. + * Optionally, the DATA command can also be sent. + * + * @param expeditor expeditor mailbox + * @param recipients list of recipient mailboxes + * @param sender envelope sender (if empty, expeditor will be used) + * @param sendDATACommand if true, the DATA command will be sent + * @param size message size, in bytes (or 0, if not known) + */ + void sendEnvelope + (const mailbox& expeditor, + const mailboxList& recipients, + const mailbox& sender, + bool sendDATACommand, + const size_t size); + + + shared_ptr <SMTPConnection> m_connection; + + + const bool m_isSMTPS; + + bool m_needReset; + + // Service infos + static SMTPServiceInfos sm_infos; +}; + + +} // smtp +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP + +#endif // VMIME_NET_SMTP_SMTPTRANSPORT_HPP_INCLUDED diff --git a/src/vmime/net/smtp/smtp.hpp b/src/vmime/net/smtp/smtp.hpp new file mode 100644 index 00000000..2a9ee312 --- /dev/null +++ b/src/vmime/net/smtp/smtp.hpp @@ -0,0 +1,33 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_SMTP_SMTP_HPP_INCLUDED +#define VMIME_NET_SMTP_SMTP_HPP_INCLUDED + + +#include "vmime/net/smtp/SMTPTransport.hpp" +#include "vmime/net/smtp/SMTPSTransport.hpp" +#include "vmime/net/smtp/SMTPExceptions.hpp" + + +#endif // VMIME_NET_SMTP_SMTP_HPP_INCLUDED diff --git a/src/vmime/net/socket.hpp b/src/vmime/net/socket.hpp new file mode 100644 index 00000000..537c34bb --- /dev/null +++ b/src/vmime/net/socket.hpp @@ -0,0 +1,184 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_SOCKET_HPP_INCLUDED +#define VMIME_NET_SOCKET_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#include "vmime/base.hpp" + +#include "vmime/net/timeoutHandler.hpp" + + +namespace vmime { +namespace net { + + +/** Interface for connecting to servers. + */ + +class VMIME_EXPORT socket : public object +{ +public: + + enum Status + { + STATUS_WOULDBLOCK = 0x1 /**< The receive operation would block. */ + }; + + + virtual ~socket() { } + + + /** Connect to the specified address and port. + * + * @param address server address (this can be a full qualified domain name + * or an IP address, doesn't matter) + * @param port server port + */ + virtual void connect(const string& address, const port_t port) = 0; + + /** Disconnect from the server. + */ + virtual void disconnect() = 0; + + /** Test whether this socket is connected. + * + * @return true if the socket is connected, false otherwise + */ + virtual bool isConnected() const = 0; + + /** Receive text data from the socket. + * + * @param buffer buffer in which to write received data + */ + virtual void receive(string& buffer) = 0; + + /** Receive raw data from the socket. + * + * @param buffer buffer in which to write received data + * @param count maximum number of bytes to receive (size of buffer) + * @return number of bytes received/written into output buffer + */ + virtual size_t receiveRaw(byte_t* buffer, const size_t count) = 0; + + /** Send text data to the socket. + * + * @param buffer data to send + */ + virtual void send(const string& buffer) = 0; + + /** Send text data to the socket. + * + * @param str null-terminated string + */ + virtual void send(const char* str) = 0; + + /** Send raw data to the socket. + * + * @param buffer data to send + * @param count number of bytes to send (size of buffer) + */ + virtual void sendRaw(const byte_t* buffer, const size_t count) = 0; + + /** Send raw data to the socket. + * Function may returns before all data is sent. + * + * @param buffer data to send + * @param count number of bytes to send (size of buffer) + * @return number of bytes sent + */ + virtual size_t sendRawNonBlocking(const byte_t* buffer, const size_t count) = 0; + + /** Return the preferred maximum block size when reading + * from or writing to this stream. + * + * @return block size, in bytes + */ + virtual size_t getBlockSize() const = 0; + + /** Return the current status of this socket. + * + * @return status flags for this socket + */ + virtual unsigned int getStatus() const = 0; + + /** Return the hostname of peer this socket is connected to. + * + * @return name of the peer, or numeric address if it cannot be found + */ + virtual const string getPeerName() const = 0; + + /** Return the address of peer this socket is connected to. + * + * @return numeric address of the peer + */ + virtual const string getPeerAddress() const = 0; + +protected: + + socket() { } + +private: + + socket(const socket&) : object() { } +}; + + +/** A class to create 'socket' objects. + */ + +class socketFactory : public object +{ +public: + + virtual ~socketFactory() { } + + /** Creates a socket without timeout handler. + * + * @return a new socket + */ + virtual shared_ptr <socket> create() = 0; + + /** Creates a socket with the specified timeout handler. + * + * @param th timeout handler + * @return a new socket + */ + virtual shared_ptr <socket> create(shared_ptr <timeoutHandler> th) = 0; +}; + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + +#endif // VMIME_NET_SOCKET_HPP_INCLUDED diff --git a/src/vmime/net/store.hpp b/src/vmime/net/store.hpp new file mode 100644 index 00000000..37dcadbc --- /dev/null +++ b/src/vmime/net/store.hpp @@ -0,0 +1,114 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_STORE_HPP_INCLUDED +#define VMIME_NET_STORE_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#include "vmime/net/service.hpp" +#include "vmime/net/folder.hpp" + + +namespace vmime { +namespace net { + + +/** A store service. + * Encapsulate protocols that provide access to user's mail drop. + */ + +class VMIME_EXPORT store : public service +{ +protected: + + store(shared_ptr <session> sess, const serviceInfos& infos, shared_ptr <security::authenticator> auth) + : service(sess, infos, auth) { } + +public: + + /** Return the default folder. This is protocol dependent + * and usually is the INBOX folder. + * + * @return default folder + */ + virtual shared_ptr <folder> getDefaultFolder() = 0; + + /** Return the root folder. This is protocol dependent + * and usually is the user's mail drop root folder. + * + * @return root folder + */ + virtual shared_ptr <folder> getRootFolder() = 0; + + /** Return the folder specified by the path. + * + * @param path absolute folder path + * @return folder at the specified path + */ + virtual shared_ptr <folder> getFolder(const folder::path& path) = 0; + + /** Test whether the specified folder name is a syntactically + * a valid name. + * + * @return true if the specified folder name is valid, false otherwise + */ + virtual bool isValidFolderName(const folder::path::component& name) const = 0; + + /** Store capabilities. */ + enum Capabilities + { + CAPABILITY_CREATE_FOLDER = (1 << 0), /**< Can create folders. */ + CAPABILITY_RENAME_FOLDER = (1 << 1), /**< Can rename folders. */ + CAPABILITY_ADD_MESSAGE = (1 << 2), /**< Can append message to folders. */ + CAPABILITY_COPY_MESSAGE = (1 << 3), /**< Can copy messages from a folder to another one. */ + CAPABILITY_DELETE_MESSAGE = (1 << 4), /**< Can delete messages. */ + CAPABILITY_PARTIAL_FETCH = (1 << 5), /**< Is partial fetch supported? */ + CAPABILITY_MESSAGE_FLAGS = (1 << 6), /**< Can set flags on messages. */ + CAPABILITY_EXTRACT_PART = (1 << 7) /**< Can extract a specific part of the message. */ + }; + + /** Return the features supported by this service. This is + * a combination of store::CAPABILITY_xxx flags. + * + * @return features supported by this service + */ + virtual int getCapabilities() const = 0; + + + Type getType() const { return (TYPE_STORE); } +}; + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + +#endif // VMIME_NET_STORE_HPP_INCLUDED diff --git a/src/vmime/net/timeoutHandler.hpp b/src/vmime/net/timeoutHandler.hpp new file mode 100644 index 00000000..24129701 --- /dev/null +++ b/src/vmime/net/timeoutHandler.hpp @@ -0,0 +1,89 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_TIMEOUTHANDLER_HPP_INCLUDED +#define VMIME_NET_TIMEOUTHANDLER_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#include "vmime/types.hpp" + + +namespace vmime { +namespace net { + + +/** A class to manage time-out in messaging services. + */ + +class VMIME_EXPORT timeoutHandler : public object +{ +public: + + virtual ~timeoutHandler() { } + + /** Called to test if the time limit has been reached. + * + * @return true if the time-out delay is elapsed + */ + virtual bool isTimeOut() = 0; + + /** Called to reset the time-out counter. + */ + virtual void resetTimeOut() = 0; + + /** Called when the time limit has been reached (when + * isTimeOut() returned true). + * + * @return true to continue (and reset the time-out) + * or false to cancel the current operation + */ + virtual bool handleTimeOut() = 0; +}; + + +/** A class to create 'timeoutHandler' objects. + */ + +class timeoutHandlerFactory : public object +{ +public: + + virtual ~timeoutHandlerFactory() { } + + virtual shared_ptr <timeoutHandler> create() = 0; +}; + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + +#endif // VMIME_NET_TIMEOUTHANDLER_HPP_INCLUDED diff --git a/src/vmime/net/tls/TLSProperties.cpp b/src/vmime/net/tls/TLSProperties.cpp new file mode 100644 index 00000000..1986db79 --- /dev/null +++ b/src/vmime/net/tls/TLSProperties.cpp @@ -0,0 +1,44 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT + + +#include "vmime/net/tls/TLSProperties.hpp" + + +namespace vmime { +namespace net { +namespace tls { + + +} // tls +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT + diff --git a/src/vmime/net/tls/TLSProperties.hpp b/src/vmime/net/tls/TLSProperties.hpp new file mode 100644 index 00000000..0dbc8f05 --- /dev/null +++ b/src/vmime/net/tls/TLSProperties.hpp @@ -0,0 +1,105 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_TLS_TLSPROPERTIES_HPP_INCLUDED +#define VMIME_NET_TLS_TLSPROPERTIES_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT + + +#include "vmime/types.hpp" + + +namespace vmime { +namespace net { +namespace tls { + + +/** Holds options for a TLS session. + */ +class VMIME_EXPORT TLSProperties : public object +{ +public: + + TLSProperties(); + TLSProperties(const TLSProperties&); + + + /** Predefined generic cipher suites (work with all TLS libraries). */ + enum GenericCipherSuite + { + CIPHERSUITE_HIGH, /**< High encryption cipher suites (> 128 bits). */ + CIPHERSUITE_MEDIUM, /**< Medium encryption cipher suites (>= 128 bits). */ + CIPHERSUITE_LOW, /**< Low encryption cipher suites (>= 64 bits). */ + + CIPHERSUITE_DEFAULT /**< Default cipher suite. */ + }; + + /** Sets the cipher suite preferences for a SSL/TLS session, using + * predefined, generic suites. This works with all underlying TLS + * libraries (OpenSSL and GNU TLS). + * + * @param cipherSuite predefined cipher suite + */ + void setCipherSuite(const GenericCipherSuite cipherSuite); + + /** Sets the cipher suite preferences for a SSL/TLS session, using + * a character string. The format and meaning of the string depend + * on the underlying TLS library. + * + * For GNU TLS, read this: + * http://gnutls.org/manual/html_node/Priority-Strings.html + * + * For OpenSSL, read this: + * http://www.openssl.org/docs/apps/ciphers.html#CIPHER_STRINGS + * + * @param cipherSuite cipher suite as a string + */ + void setCipherSuite(const string& cipherSuite); + + /** Returns the cipher suite preferences for a SSL/TLS session, as + * a character string. The format and meaning of the string depend + * on the underlying TLS library (see setCipherSuite() method). + * + * @return cipher suite string + */ + const string getCipherSuite() const; + +private: + + shared_ptr <object> m_data; +}; + + +} // tls +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT + +#endif // VMIME_NET_TLS_TLSPROPERTIES_HPP_INCLUDED diff --git a/src/vmime/net/tls/TLSSecuredConnectionInfos.cpp b/src/vmime/net/tls/TLSSecuredConnectionInfos.cpp new file mode 100644 index 00000000..4856e9af --- /dev/null +++ b/src/vmime/net/tls/TLSSecuredConnectionInfos.cpp @@ -0,0 +1,72 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT + + +#include "vmime/net/tls/TLSSecuredConnectionInfos.hpp" +#include "vmime/net/tls/TLSSession.hpp" + + +namespace vmime { +namespace net { +namespace tls { + + +TLSSecuredConnectionInfos::TLSSecuredConnectionInfos + (const string& host, const port_t port, + shared_ptr <TLSSession> tlsSession, shared_ptr <TLSSocket> tlsSocket) + : m_host(host), m_port(port), + m_tlsSession(tlsSession), m_tlsSocket(tlsSocket) +{ +} + + +const string TLSSecuredConnectionInfos::getHost() const +{ + return m_host; +} + + +port_t TLSSecuredConnectionInfos::getPort() const +{ + return m_port; +} + + +shared_ptr <const security::cert::certificateChain> TLSSecuredConnectionInfos::getPeerCertificates() const +{ + return m_tlsSocket->getPeerCertificates(); +} + + +} // tls +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT + diff --git a/src/vmime/net/tls/TLSSecuredConnectionInfos.hpp b/src/vmime/net/tls/TLSSecuredConnectionInfos.hpp new file mode 100644 index 00000000..e552d6f9 --- /dev/null +++ b/src/vmime/net/tls/TLSSecuredConnectionInfos.hpp @@ -0,0 +1,84 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_TLSSECUREDCONNECTIONINFOS_HPP_INCLUDED +#define VMIME_NET_TLSSECUREDCONNECTIONINFOS_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT + + +#include "vmime/net/securedConnectionInfos.hpp" + +#include "vmime/security/cert/certificateChain.hpp" + + +namespace vmime { +namespace net { +namespace tls { + + +class TLSSession; +class TLSSocket; + + +/** Information about a TLS-secured connection used by a service. + */ +class VMIME_EXPORT TLSSecuredConnectionInfos : public securedConnectionInfos +{ +public: + + TLSSecuredConnectionInfos(const string& host, const port_t port, + shared_ptr <TLSSession> tlsSession, shared_ptr <TLSSocket> tlsSocket); + + const string getHost() const; + port_t getPort() const; + + /** Return the peer's certificate (chain) as sent by the peer. + * + * @return server certificate chain + */ + shared_ptr <const security::cert::certificateChain> getPeerCertificates() const; + +private: + + string m_host; + port_t m_port; + + shared_ptr <TLSSession> m_tlsSession; + shared_ptr <TLSSocket> m_tlsSocket; +}; + + +} // tls +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT + +#endif // VMIME_NET_TLSSECUREDCONNECTIONINFOS_HPP_INCLUDED + diff --git a/src/vmime/net/tls/TLSSession.cpp b/src/vmime/net/tls/TLSSession.cpp new file mode 100644 index 00000000..a46f07ca --- /dev/null +++ b/src/vmime/net/tls/TLSSession.cpp @@ -0,0 +1,48 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT + + +#include "vmime/net/tls/TLSSession.hpp" + + +namespace vmime { +namespace net { +namespace tls { + + +TLSSession::TLSSession() +{ +} + + +} // tls +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT diff --git a/src/vmime/net/tls/TLSSession.hpp b/src/vmime/net/tls/TLSSession.hpp new file mode 100644 index 00000000..9e061f89 --- /dev/null +++ b/src/vmime/net/tls/TLSSession.hpp @@ -0,0 +1,93 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_TLS_TLSSESSION_HPP_INCLUDED +#define VMIME_NET_TLS_TLSSESSION_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT + + +#include "vmime/types.hpp" + +#include "vmime/net/tls/TLSSocket.hpp" +#include "vmime/net/tls/TLSProperties.hpp" + +#include "vmime/security/cert/certificateVerifier.hpp" + + +namespace vmime { +namespace net { +namespace tls { + + +/** Describe a TLS connection between a client and a server. + */ +class VMIME_EXPORT TLSSession : public object +{ +public: + + /** Create and initialize a new TLS session. + * + * @param cv object responsible for verifying certificates + * sent by the server + * @param props TLS properties for this session + * @return a new TLS session + */ + static shared_ptr <TLSSession> create(shared_ptr <security::cert::certificateVerifier> cv, shared_ptr <TLSProperties> props); + + /** Create a new socket that adds a TLS security layer around + * an existing socket. You should create only one socket + * per session. + * + * @param sok socket to wrap + * @return TLS socket wrapper + */ + virtual shared_ptr <TLSSocket> getSocket(shared_ptr <socket> sok) = 0; + + /** Get the object responsible for verifying certificates when + * using secured connections (TLS/SSL). + */ + virtual shared_ptr <security::cert::certificateVerifier> getCertificateVerifier() = 0; + +protected: + + TLSSession(); + +private: + + TLSSession(const TLSSession&); +}; + + +} // tls +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT + +#endif // VMIME_NET_TLS_TLSSESSION_HPP_INCLUDED diff --git a/src/vmime/net/tls/TLSSocket.cpp b/src/vmime/net/tls/TLSSocket.cpp new file mode 100644 index 00000000..0419a571 --- /dev/null +++ b/src/vmime/net/tls/TLSSocket.cpp @@ -0,0 +1,44 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT + + +#include "vmime/net/tls/TLSSocket.hpp" + + +namespace vmime { +namespace net { +namespace tls { + + +} // tls +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT + diff --git a/src/vmime/net/tls/TLSSocket.hpp b/src/vmime/net/tls/TLSSocket.hpp new file mode 100644 index 00000000..e2668ad4 --- /dev/null +++ b/src/vmime/net/tls/TLSSocket.hpp @@ -0,0 +1,88 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_TLS_TLSSOCKET_HPP_INCLUDED +#define VMIME_NET_TLS_TLSSOCKET_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT + + +#include "vmime/exception.hpp" + +#include "vmime/net/socket.hpp" +#include "vmime/net/timeoutHandler.hpp" + +#include "vmime/security/cert/certificateChain.hpp" + + +namespace vmime { +namespace net { +namespace tls { + + +class TLSSession; + + +/** Add a TLS security layer to an existing socket. + */ +class VMIME_EXPORT TLSSocket : public socket +{ +public: + + /** Create a new socket object that adds a security layer + * around an existing socket. + * + * @param session TLS session + * @param sok socket to wrap + */ + static shared_ptr <TLSSocket> wrap(shared_ptr <TLSSession> session, shared_ptr <socket> sok); + + /** Starts a TLS handshake on this connection. + * + * @throw exceptions::tls_exception if a fatal error occurs + * during the negociation process, exceptions::operation_timed_out + * if a time-out occurs + */ + virtual void handshake(shared_ptr <timeoutHandler> toHandler = null) = 0; + + /** Return the peer's certificate (chain) as sent by the peer. + * + * @return server certificate chain, or NULL if the handshake + * has not been performed yet + */ + virtual shared_ptr <security::cert::certificateChain> getPeerCertificates() const = 0; +}; + + +} // tls +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT + +#endif // VMIME_NET_TLS_TLSSOCKET_HPP_INCLUDED diff --git a/src/vmime/net/tls/gnutls/TLSProperties_GnuTLS.cpp b/src/vmime/net/tls/gnutls/TLSProperties_GnuTLS.cpp new file mode 100644 index 00000000..36ab7d7a --- /dev/null +++ b/src/vmime/net/tls/gnutls/TLSProperties_GnuTLS.cpp @@ -0,0 +1,113 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_GNUTLS + + +#include "vmime/base.hpp" +#include "vmime/net/tls/gnutls/TLSProperties_GnuTLS.hpp" + +#include <gnutls/gnutls.h> +#if GNUTLS_VERSION_NUMBER < 0x030000 +#include <gnutls/extra.h> +#endif + + +namespace vmime { +namespace net { +namespace tls { + + +TLSProperties::TLSProperties() + : m_data(make_shared <TLSProperties_GnuTLS>()) +{ + setCipherSuite(CIPHERSUITE_DEFAULT); +} + + +TLSProperties::TLSProperties(const TLSProperties& props) + : object(), + m_data(make_shared <TLSProperties_GnuTLS>()) +{ + *dynamicCast <TLSProperties_GnuTLS>(m_data) = *dynamicCast <TLSProperties_GnuTLS>(props.m_data); +} + + +void TLSProperties::setCipherSuite(const GenericCipherSuite cipherSuite) +{ + switch (cipherSuite) + { + case CIPHERSUITE_HIGH: + + setCipherSuite("SECURE256:%SSL3_RECORD_VERSION"); + break; + + case CIPHERSUITE_MEDIUM: + + setCipherSuite("SECURE128:%SSL3_RECORD_VERSION"); + break; + + case CIPHERSUITE_LOW: + + setCipherSuite("NORMAL:%SSL3_RECORD_VERSION"); + break; + + default: + case CIPHERSUITE_DEFAULT: + + setCipherSuite("NORMAL:%SSL3_RECORD_VERSION"); + break; + } +} + + +void TLSProperties::setCipherSuite(const string& cipherSuite) +{ + dynamicCast <TLSProperties_GnuTLS>(m_data)->cipherSuite = cipherSuite; +} + + +const string TLSProperties::getCipherSuite() const +{ + return dynamicCast <TLSProperties_GnuTLS>(m_data)->cipherSuite; +} + + + +TLSProperties_GnuTLS& TLSProperties_GnuTLS::operator=(const TLSProperties_GnuTLS& other) +{ + cipherSuite = other.cipherSuite; + + return *this; +} + + +} // tls +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_GNUTLS diff --git a/src/vmime/net/tls/gnutls/TLSProperties_GnuTLS.hpp b/src/vmime/net/tls/gnutls/TLSProperties_GnuTLS.hpp new file mode 100644 index 00000000..2038778a --- /dev/null +++ b/src/vmime/net/tls/gnutls/TLSProperties_GnuTLS.hpp @@ -0,0 +1,68 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_TLS_TLSPROPERTIES_GNUTLS_HPP_INCLUDED +#define VMIME_NET_TLS_TLSPROPERTIES_GNUTLS_HPP_INCLUDED + + +#ifndef VMIME_BUILDING_DOC + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_GNUTLS + + +#include "vmime/types.hpp" + +#include "vmime/net/tls/TLSProperties.hpp" + + +namespace vmime { +namespace net { +namespace tls { + + +class TLSProperties_GnuTLS : public object +{ +public: + + TLSProperties_GnuTLS& operator=(const TLSProperties_GnuTLS& other); + + + string cipherSuite; +}; + + +} // tls +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_GNUTLS + +#endif // VMIME_BUILDING_DOC + +#endif // VMIME_NET_TLS_TLSPROPERTIES_GNUTLS_HPP_INCLUDED + diff --git a/src/vmime/net/tls/gnutls/TLSSession_GnuTLS.cpp b/src/vmime/net/tls/gnutls/TLSSession_GnuTLS.cpp new file mode 100644 index 00000000..1c520ed1 --- /dev/null +++ b/src/vmime/net/tls/gnutls/TLSSession_GnuTLS.cpp @@ -0,0 +1,300 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_GNUTLS + + +#include <gnutls/gnutls.h> +#if GNUTLS_VERSION_NUMBER < 0x030000 +#include <gnutls/extra.h> +#endif + + +// Dependency on gcrypt is not needed since GNU TLS version 2.12. +// See here: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=638651 +#if GNUTLS_VERSION_NUMBER <= 0x020b00 +# define VMIME_GNUTLS_NEEDS_GCRYPT 1 +#endif + +#if VMIME_HAVE_PTHREAD +# include <pthread.h> +# if VMIME_GNUTLS_NEEDS_GCRYPT +# include <gcrypt.h> +# endif +# include <errno.h> +#endif // VMIME_HAVE_PTHREAD + +#include "vmime/net/tls/gnutls/TLSSession_GnuTLS.hpp" +#include "vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp" +#include "vmime/net/tls/gnutls/TLSProperties_GnuTLS.hpp" + +#include "vmime/exception.hpp" + + +// Enable GnuTLS debugging by defining GNUTLS_DEBUG +//#define GNUTLS_DEBUG 1 + + +#include <sstream> +#include <iomanip> + +#if VMIME_DEBUG && GNUTLS_DEBUG + #include <iostream> +#endif // VMIME_DEBUG && GNUTLS_DEBUG + + +#if VMIME_HAVE_PTHREAD && VMIME_GNUTLS_NEEDS_GCRYPT && defined(GCRY_THREAD_OPTION_PTHREAD_IMPL) +extern "C" +{ + GCRY_THREAD_OPTION_PTHREAD_IMPL; +} +#endif // VMIME_HAVE_PTHREAD && defined(GCRY_THREAD_OPTION_PTHREAD_IMPL + + +namespace vmime { +namespace net { +namespace tls { + + +#ifndef VMIME_BUILDING_DOC + +// Initialize GNU TLS library +struct TLSGlobal +{ + TLSGlobal() + { +#if VMIME_HAVE_PTHREAD && defined(GCRY_THREAD_OPTION_PTHREAD_IMPL) + #if VMIME_GNUTLS_NEEDS_GCRYPT + gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); + #endif // VMIME_GNUTLS_NEEDS_GCRYPT +#endif // VMIME_HAVE_PTHREAD && defined(GCRY_THREAD_OPTION_PTHREAD_IMPL + + gnutls_global_init(); + //gnutls_global_init_extra(); + +#if VMIME_DEBUG && GNUTLS_DEBUG + gnutls_global_set_log_function(TLSLogFunc); + gnutls_global_set_log_level(10); +#endif // VMIME_DEBUG && GNUTLS_DEBUG + + gnutls_anon_allocate_client_credentials(&anonCred); + gnutls_certificate_allocate_credentials(&certCred); + } + + ~TLSGlobal() + { + gnutls_anon_free_client_credentials(anonCred); + gnutls_certificate_free_credentials(certCred); + + gnutls_global_deinit(); + } + +#if VMIME_DEBUG && GNUTLS_DEBUG + + static void TLSLogFunc(int level, const char *str) + { + std::cerr << "GNUTLS: [" << level << "] " << str << std::endl; + } + +#endif // VMIME_DEBUG && GNUTLS_DEBUG + + + gnutls_anon_client_credentials anonCred; + gnutls_certificate_credentials certCred; +}; + +static TLSGlobal g_gnutlsGlobal; + + +#endif // VMIME_BUILDING_DOC + + + +// static +shared_ptr <TLSSession> TLSSession::create(shared_ptr <security::cert::certificateVerifier> cv, shared_ptr <TLSProperties> props) +{ + return make_shared <TLSSession_GnuTLS>(cv, props); +} + + +TLSSession_GnuTLS::TLSSession_GnuTLS(shared_ptr <security::cert::certificateVerifier> cv, shared_ptr <TLSProperties> props) + : m_certVerifier(cv), m_props(props) +{ + int res; + + m_gnutlsSession = new gnutls_session; + + if (gnutls_init(m_gnutlsSession, GNUTLS_CLIENT) != 0) + throw std::bad_alloc(); + + // Sets some default priority on the ciphers, key exchange methods, + // macs and compression methods. +#ifdef VMIME_HAVE_GNUTLS_PRIORITY_FUNCS + gnutls_dh_set_prime_bits(*m_gnutlsSession, 128); + + if ((res = gnutls_priority_set_direct + (*m_gnutlsSession, m_props->getCipherSuite().c_str(), NULL)) != 0) + { + throwTLSException("gnutls_priority_set_direct", res); + } + +#else // !VMIME_HAVE_GNUTLS_PRIORITY_FUNCS + + gnutls_set_default_priority(*m_gnutlsSession); + + // Sets the priority on the certificate types supported by gnutls. + // Priority is higher for types specified before others. After + // specifying the types you want, you must append a 0. + const int certTypePriority[] = { GNUTLS_CRT_X509, 0 }; + + res = gnutls_certificate_type_set_priority + (*m_gnutlsSession, certTypePriority); + + if (res < 0) + { + throwTLSException + ("gnutls_certificate_type_set_priority", res); + } + + // Sets the priority on the protocol types + const int protoPriority[] = { GNUTLS_TLS1, GNUTLS_SSL3, 0 }; + + res = gnutls_protocol_set_priority(*m_gnutlsSession, protoPriority); + + if (res < 0) + { + throwTLSException + ("gnutls_certificate_type_set_priority", res); + } + + // Priority on the ciphers + const int cipherPriority[] = + { + GNUTLS_CIPHER_ARCFOUR_128, + GNUTLS_CIPHER_3DES_CBC, + GNUTLS_CIPHER_AES_128_CBC, + GNUTLS_CIPHER_AES_256_CBC, + GNUTLS_CIPHER_ARCFOUR_40, + GNUTLS_CIPHER_RC2_40_CBC, + GNUTLS_CIPHER_DES_CBC, + 0 + }; + + gnutls_cipher_set_priority(*m_gnutlsSession, cipherPriority); + + // Priority on MACs + const int macPriority[] = { GNUTLS_MAC_SHA, GNUTLS_MAC_MD5, 0}; + + gnutls_mac_set_priority(*m_gnutlsSession, macPriority); + + // Priority on key exchange methods + const int kxPriority[] = + { + GNUTLS_KX_RSA, + GNUTLS_KX_DHE_DSS, + GNUTLS_KX_DHE_RSA, + GNUTLS_KX_ANON_DH, + GNUTLS_KX_SRP, + GNUTLS_KX_RSA_EXPORT, + GNUTLS_KX_SRP_RSA, + GNUTLS_KX_SRP_DSS, + 0 + }; + + gnutls_kx_set_priority(*m_gnutlsSession, kxPriority); + + // Priority on compression methods + const int compressionPriority[] = + { + GNUTLS_COMP_ZLIB, + //GNUTLS_COMP_LZO, + GNUTLS_COMP_NULL, + 0 + }; + + gnutls_compression_set_priority(*m_gnutlsSession, compressionPriority); + +#endif // !VMIME_HAVE_GNUTLS_PRIORITY_FUNCS + + // Initialize credentials + gnutls_credentials_set(*m_gnutlsSession, + GNUTLS_CRD_ANON, g_gnutlsGlobal.anonCred); + + gnutls_credentials_set(*m_gnutlsSession, + GNUTLS_CRD_CERTIFICATE, g_gnutlsGlobal.certCred); +} + + +TLSSession_GnuTLS::TLSSession_GnuTLS(const TLSSession_GnuTLS&) + : TLSSession() +{ + // Not used +} + + +TLSSession_GnuTLS::~TLSSession_GnuTLS() +{ + if (m_gnutlsSession) + { + gnutls_deinit(*m_gnutlsSession); + + delete m_gnutlsSession; + m_gnutlsSession = NULL; + } +} + + +shared_ptr <TLSSocket> TLSSession_GnuTLS::getSocket(shared_ptr <socket> sok) +{ + return TLSSocket::wrap(dynamicCast <TLSSession>(shared_from_this()), sok); +} + + +shared_ptr <security::cert::certificateVerifier> TLSSession_GnuTLS::getCertificateVerifier() +{ + return m_certVerifier; +} + + +void TLSSession_GnuTLS::throwTLSException(const string& fname, const int code) +{ + std::ostringstream msg; + + msg << fname + "() returned code "; + msg << std::hex << code; + msg << ": "; + msg << gnutls_strerror(code); + + throw exceptions::tls_exception(msg.str()); +} + + +} // tls +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_GNUTLS diff --git a/src/vmime/net/tls/gnutls/TLSSession_GnuTLS.hpp b/src/vmime/net/tls/gnutls/TLSSession_GnuTLS.hpp new file mode 100644 index 00000000..7f762b58 --- /dev/null +++ b/src/vmime/net/tls/gnutls/TLSSession_GnuTLS.hpp @@ -0,0 +1,91 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_TLS_TLSSESSION_GNUTLS_HPP_INCLUDED +#define VMIME_NET_TLS_TLSSESSION_GNUTLS_HPP_INCLUDED + + +#ifndef VMIME_BUILDING_DOC + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_GNUTLS + + +#include "vmime/types.hpp" + +#include "vmime/net/tls/TLSSession.hpp" +#include "vmime/net/tls/TLSSocket.hpp" +#include "vmime/net/tls/TLSProperties.hpp" + + +namespace vmime { +namespace net { +namespace tls { + + +class TLSSession_GnuTLS : public TLSSession +{ + friend class TLSSocket_GnuTLS; + +public: + + TLSSession_GnuTLS(shared_ptr <security::cert::certificateVerifier> cv, shared_ptr <TLSProperties> props); + ~TLSSession_GnuTLS(); + + + shared_ptr <TLSSocket> getSocket(shared_ptr <socket> sok); + + shared_ptr <security::cert::certificateVerifier> getCertificateVerifier(); + +private: + + TLSSession_GnuTLS(const TLSSession_GnuTLS&); + + static void throwTLSException(const string& fname, const int code); + + +#ifdef LIBGNUTLS_VERSION + gnutls_session* m_gnutlsSession; +#else + void* m_gnutlsSession; +#endif // LIBGNUTLS_VERSION + + shared_ptr <security::cert::certificateVerifier> m_certVerifier; + shared_ptr <TLSProperties> m_props; +}; + + +} // tls +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_GNUTLS + +#endif // VMIME_BUILDING_DOC + +#endif // VMIME_NET_TLS_TLSSESSION_GNUTLS_HPP_INCLUDED + diff --git a/src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.cpp b/src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.cpp new file mode 100644 index 00000000..5a90565b --- /dev/null +++ b/src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.cpp @@ -0,0 +1,490 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_GNUTLS + + +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> + +#include "vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp" +#include "vmime/net/tls/gnutls/TLSSession_GnuTLS.hpp" + +#include "vmime/platform.hpp" + +#include "vmime/security/cert/X509Certificate.hpp" + +#include "vmime/utility/stringUtils.hpp" + +#include <cstring> + + +namespace vmime { +namespace net { +namespace tls { + + +// static +shared_ptr <TLSSocket> TLSSocket::wrap(shared_ptr <TLSSession> session, shared_ptr <socket> sok) +{ + return make_shared <TLSSocket_GnuTLS> + (dynamicCast <TLSSession_GnuTLS>(session), sok); +} + + +TLSSocket_GnuTLS::TLSSocket_GnuTLS(shared_ptr <TLSSession_GnuTLS> session, shared_ptr <socket> sok) + : m_session(session), m_wrapped(sok), m_connected(false), + m_handshaking(false), m_ex(NULL), m_status(0) +{ + gnutls_transport_set_ptr(*m_session->m_gnutlsSession, this); + + gnutls_transport_set_push_function(*m_session->m_gnutlsSession, gnutlsPushFunc); + gnutls_transport_set_pull_function(*m_session->m_gnutlsSession, gnutlsPullFunc); +} + + +TLSSocket_GnuTLS::~TLSSocket_GnuTLS() +{ + if (m_ex) + { + delete m_ex; + m_ex = NULL; + } + + try + { + disconnect(); + } + catch (...) + { + // Don't throw exception in destructor + } +} + + +void TLSSocket_GnuTLS::connect(const string& address, const port_t port) +{ + m_wrapped->connect(address, port); + + handshake(null); + + m_connected = true; +} + + +void TLSSocket_GnuTLS::disconnect() +{ + if (m_connected) + { + gnutls_bye(*m_session->m_gnutlsSession, GNUTLS_SHUT_RDWR); + + m_wrapped->disconnect(); + + m_connected = false; + } +} + + +bool TLSSocket_GnuTLS::isConnected() const +{ + return m_wrapped->isConnected() && m_connected; +} + + +size_t TLSSocket_GnuTLS::getBlockSize() const +{ + return 16384; // 16 KB +} + + +const string TLSSocket_GnuTLS::getPeerName() const +{ + return m_wrapped->getPeerName(); +} + + +const string TLSSocket_GnuTLS::getPeerAddress() const +{ + return m_wrapped->getPeerAddress(); +} + + +void TLSSocket_GnuTLS::receive(string& buffer) +{ + const size_t size = receiveRaw(m_buffer, sizeof(m_buffer)); + buffer = utility::stringUtils::makeStringFromBytes(m_buffer, size); +} + + +void TLSSocket_GnuTLS::send(const string& buffer) +{ + sendRaw(reinterpret_cast <const byte_t*>(buffer.data()), buffer.length()); +} + + +void TLSSocket_GnuTLS::send(const char* str) +{ + sendRaw(reinterpret_cast <const byte_t*>(str), ::strlen(str)); +} + + +size_t TLSSocket_GnuTLS::receiveRaw(byte_t* buffer, const size_t count) +{ + m_status &= ~STATUS_WOULDBLOCK; + + const ssize_t ret = gnutls_record_recv + (*m_session->m_gnutlsSession, + buffer, static_cast <size_t>(count)); + + if (m_ex) + internalThrow(); + + if (ret < 0) + { + if (ret == GNUTLS_E_AGAIN) + { + m_status |= STATUS_WOULDBLOCK; + return 0; + } + + TLSSession_GnuTLS::throwTLSException("gnutls_record_recv", static_cast <int>(ret)); + } + + return static_cast <size_t>(ret); +} + + +void TLSSocket_GnuTLS::sendRaw(const byte_t* buffer, const size_t count) +{ + ssize_t ret = gnutls_record_send + (*m_session->m_gnutlsSession, + buffer, static_cast <size_t>(count)); + + if (m_ex) + internalThrow(); + + if (ret < 0) + { + if (ret == GNUTLS_E_AGAIN) + { + m_status |= STATUS_WOULDBLOCK; + return; + } + + TLSSession_GnuTLS::throwTLSException("gnutls_record_send", static_cast <int>(ret)); + } +} + + +size_t TLSSocket_GnuTLS::sendRawNonBlocking(const byte_t* buffer, const size_t count) +{ + ssize_t ret = gnutls_record_send + (*m_session->m_gnutlsSession, + buffer, static_cast <size_t>(count)); + + if (m_ex) + internalThrow(); + + if (ret < 0) + { + if (ret == GNUTLS_E_AGAIN) + { + m_status |= STATUS_WOULDBLOCK; + return 0; + } + + TLSSession_GnuTLS::throwTLSException("gnutls_record_send", static_cast <int>(ret)); + } + + return static_cast <size_t>(ret); +} + + +unsigned int TLSSocket_GnuTLS::getStatus() const +{ + return m_status | m_wrapped->getStatus(); +} + + +void TLSSocket_GnuTLS::handshake(shared_ptr <timeoutHandler> toHandler) +{ + if (toHandler) + toHandler->resetTimeOut(); + + // Start handshaking process + m_handshaking = true; + m_toHandler = toHandler; + + try + { + while (true) + { + const int ret = gnutls_handshake(*m_session->m_gnutlsSession); + + if (m_ex) + internalThrow(); + + if (ret < 0) + { + if (ret == GNUTLS_E_AGAIN || + ret == GNUTLS_E_INTERRUPTED) + { + // Non-fatal error + platform::getHandler()->wait(); + } + else + { + TLSSession_GnuTLS::throwTLSException("gnutls_handshake", ret); + } + } + else + { + // Successful handshake + break; + } + } + } + catch (...) + { + m_handshaking = false; + m_toHandler = null; + + throw; + } + + m_handshaking = false; + m_toHandler = null; + + // Verify server's certificate(s) + shared_ptr <security::cert::certificateChain> certs = getPeerCertificates(); + + if (certs == NULL) + throw exceptions::tls_exception("No peer certificate."); + + m_session->getCertificateVerifier()->verify(certs, getPeerName()); + + m_connected = true; +} + + +ssize_t TLSSocket_GnuTLS::gnutlsPushFunc + (gnutls_transport_ptr trspt, const void* data, size_t len) +{ + TLSSocket_GnuTLS* sok = reinterpret_cast <TLSSocket_GnuTLS*>(trspt); + + try + { + sok->m_wrapped->sendRaw + (reinterpret_cast <const byte_t*>(data), len); + } + catch (exception& e) + { + // Workaround for bad behaviour when throwing C++ exceptions + // from C functions (GNU TLS) + sok->m_ex = e.clone(); + return -1; + } + + return len; +} + + +ssize_t TLSSocket_GnuTLS::gnutlsPullFunc + (gnutls_transport_ptr trspt, void* data, size_t len) +{ + TLSSocket_GnuTLS* sok = reinterpret_cast <TLSSocket_GnuTLS*>(trspt); + + try + { + // Workaround for cross-platform asynchronous handshaking: + // gnutls_handshake() only returns GNUTLS_E_AGAIN if recv() + // returns -1 and errno is set to EGAIN... + if (sok->m_handshaking) + { + while (true) + { + const ssize_t ret = static_cast <ssize_t> + (sok->m_wrapped->receiveRaw + (reinterpret_cast <byte_t*>(data), len)); + + if (ret == 0) + { + // No data available yet + platform::getHandler()->wait(); + } + else + { + return ret; + } + + // Check whether the time-out delay is elapsed + if (sok->m_toHandler && sok->m_toHandler->isTimeOut()) + { + if (!sok->m_toHandler->handleTimeOut()) + throw exceptions::operation_timed_out(); + + sok->m_toHandler->resetTimeOut(); + } + } + } + else + { + const ssize_t n = static_cast <ssize_t> + (sok->m_wrapped->receiveRaw + (reinterpret_cast <byte_t*>(data), len)); + + if (n == 0 && sok->m_wrapped->getStatus() & socket::STATUS_WOULDBLOCK) + return GNUTLS_E_AGAIN; + + return n; + } + } + catch (exception& e) + { + // Workaround for bad behaviour when throwing C++ exceptions + // from C functions (GNU TLS) + sok->m_ex = e.clone(); + return -1; + } +} + + +shared_ptr <security::cert::certificateChain> TLSSocket_GnuTLS::getPeerCertificates() const +{ + unsigned int certCount = 0; + const gnutls_datum* rawData = gnutls_certificate_get_peers + (*m_session->m_gnutlsSession, &certCount); + + if (rawData == NULL) + return null; + + // Try X.509 + gnutls_x509_crt* x509Certs = new gnutls_x509_crt[certCount]; + + for (unsigned int i = 0; i < certCount; ++i) + { + gnutls_x509_crt_init(x509Certs + i); + + int res = gnutls_x509_crt_import(x509Certs[i], rawData + i, + GNUTLS_X509_FMT_DER); + + if (res < 0) + { + // XXX more fine-grained error reporting? + delete [] x509Certs; + return null; + } + } + + { + std::vector <shared_ptr <security::cert::certificate> > certs; + bool error = false; + + for (unsigned int i = 0 ; i < certCount ; ++i) + { + size_t dataSize = 0; + + gnutls_x509_crt_export(x509Certs[i], + GNUTLS_X509_FMT_DER, NULL, &dataSize); + + std::vector <byte_t> data(dataSize); + + gnutls_x509_crt_export(x509Certs[i], + GNUTLS_X509_FMT_DER, &data[0], &dataSize); + + shared_ptr <security::cert::X509Certificate> cert = + security::cert::X509Certificate::import(&data[0], dataSize); + + if (cert != NULL) + certs.push_back(cert); + else + error = true; + + gnutls_x509_crt_deinit(x509Certs[i]); + } + + delete [] x509Certs; + + if (error) + return null; + + return make_shared <security::cert::certificateChain>(certs); + } + + delete [] x509Certs; + + return null; +} + + +// Following is a workaround for C++ exceptions to pass correctly between +// C and C++ calls. +// +// gnutls_record_recv() calls TLSSocket::gnutlsPullFunc, and exceptions +// thrown by the socket can not be caught. + +#ifndef VMIME_BUILDING_DOC + +class TLSSocket_DeleteExWrapper : public object +{ +public: + + TLSSocket_DeleteExWrapper(exception* ex) : m_ex(ex) { } + ~TLSSocket_DeleteExWrapper() { delete m_ex; } + +private: + + exception* m_ex; +}; + +#endif // VMIME_BUILDING_DOC + + +void TLSSocket_GnuTLS::internalThrow() +{ + static std::vector <shared_ptr <TLSSocket_DeleteExWrapper> > exToDelete; + + if (m_ex) + { + // Reset the current exception pointer to prevent the same + // exception from being thrown again later + exception* ex = m_ex; + m_ex = NULL; + + // To avoid memory leaks + exToDelete.push_back(make_shared <TLSSocket_DeleteExWrapper>(ex)); + + throw *ex; + } +} + + +} // tls +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_GNUTLS diff --git a/src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp b/src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp new file mode 100644 index 00000000..885fac13 --- /dev/null +++ b/src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp @@ -0,0 +1,120 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_TLS_TLSSOCKET_GNUTLS_HPP_INCLUDED +#define VMIME_NET_TLS_TLSSOCKET_GNUTLS_HPP_INCLUDED + + +#ifndef VMIME_BUILDING_DOC + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_GNUTLS + + +#include "vmime/net/tls/TLSSocket.hpp" + + +namespace vmime { +namespace net { +namespace tls { + + +class TLSSession; +class TLSSession_GnuTLS; + + +class TLSSocket_GnuTLS : public TLSSocket +{ +public: + + TLSSocket_GnuTLS(shared_ptr <TLSSession_GnuTLS> session, shared_ptr <socket> sok); + ~TLSSocket_GnuTLS(); + + + void handshake(shared_ptr <timeoutHandler> toHandler = null); + + shared_ptr <security::cert::certificateChain> getPeerCertificates() const; + + // Implementation of 'socket' + void connect(const string& address, const port_t port); + void disconnect(); + bool isConnected() const; + + void receive(string& buffer); + size_t receiveRaw(byte_t* buffer, const size_t count); + + void send(const string& buffer); + void send(const char* str); + void sendRaw(const byte_t* buffer, const size_t count); + size_t sendRawNonBlocking(const byte_t* buffer, const size_t count); + + size_t getBlockSize() const; + + unsigned int getStatus() const; + + const string getPeerName() const; + const string getPeerAddress() const; + +private: + + void internalThrow(); + +#ifdef LIBGNUTLS_VERSION + static ssize_t gnutlsPushFunc(gnutls_transport_ptr trspt, const void* data, size_t len); + static ssize_t gnutlsPullFunc(gnutls_transport_ptr trspt, void* data, size_t len); +#else + static int gnutlsPushFunc(void* trspt, const void* data, size_t len); + static int gnutlsPullFunc(void* trspt, void* data, size_t len); +#endif // LIBGNUTLS_VERSION + + + shared_ptr <TLSSession_GnuTLS> m_session; + shared_ptr <socket> m_wrapped; + + bool m_connected; + + byte_t m_buffer[65536]; + + bool m_handshaking; + shared_ptr <timeoutHandler> m_toHandler; + + exception* m_ex; + + unsigned int m_status; +}; + + +} // tls +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_GNUTLS + +#endif // VMIME_BUILDING_DOC + +#endif // VMIME_NET_TLS_TLSSOCKET_GNUTLS_HPP_INCLUDED + diff --git a/src/vmime/net/tls/openssl/OpenSSLInitializer.cpp b/src/vmime/net/tls/openssl/OpenSSLInitializer.cpp new file mode 100644 index 00000000..1bbb9ee5 --- /dev/null +++ b/src/vmime/net/tls/openssl/OpenSSLInitializer.cpp @@ -0,0 +1,142 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL + + +#include "vmime/net/tls/openssl/OpenSSLInitializer.hpp" + +#include "vmime/utility/sync/autoLock.hpp" +#include "vmime/utility/sync/criticalSection.hpp" + +#include "vmime/platform.hpp" + +#include <openssl/ssl.h> +#include <openssl/rand.h> +#include <openssl/crypto.h> +#include <openssl/err.h> + +#if OPENSSL_VERSION_NUMBER >= 0x0907000L +# include <openssl/conf.h> +#endif + + +namespace vmime { +namespace net { +namespace tls { + + +shared_ptr <vmime::utility::sync::criticalSection >* OpenSSLInitializer::sm_mutexes; + + +OpenSSLInitializer::autoInitializer::autoInitializer() +{ + // The construction of this unique 'oneTimeInitializer' object will be triggered + // by the 'autoInitializer' objects from the other translation units + static OpenSSLInitializer::oneTimeInitializer oneTimeInitializer; +} + + +OpenSSLInitializer::autoInitializer::~autoInitializer() +{ +} + + +OpenSSLInitializer::oneTimeInitializer::oneTimeInitializer() +{ + initialize(); +} + + +OpenSSLInitializer::oneTimeInitializer::~oneTimeInitializer() +{ + uninitialize(); +} + + +// static +void OpenSSLInitializer::initialize() +{ +#if OPENSSL_VERSION_NUMBER >= 0x0907000L + OPENSSL_config(NULL); +#endif + + SSL_load_error_strings(); + SSL_library_init(); + OpenSSL_add_all_algorithms(); + + unsigned char seed[SEEDSIZE]; + vmime::platform::getHandler()->generateRandomBytes(seed, SEEDSIZE); + RAND_seed(seed, SEEDSIZE); + + int numMutexes = CRYPTO_num_locks(); + sm_mutexes = new shared_ptr <vmime::utility::sync::criticalSection>[numMutexes]; + + for (int i = 0 ; i < numMutexes ; ++i) + sm_mutexes[i] = vmime::platform::getHandler()->createCriticalSection(); + + CRYPTO_set_locking_callback(&OpenSSLInitializer::lock); + CRYPTO_set_id_callback(&OpenSSLInitializer::id); +} + + +// static +void OpenSSLInitializer::uninitialize() +{ + EVP_cleanup(); + ERR_free_strings(); + + CRYPTO_set_locking_callback(NULL); + CRYPTO_set_id_callback(NULL); + + delete [] sm_mutexes; +} + + +// static +void OpenSSLInitializer::lock(int mode, int n, const char* /* file */, int /* line */) +{ + if (mode & CRYPTO_LOCK) + sm_mutexes[n]->lock(); + else + sm_mutexes[n]->unlock(); +} + + +// static +unsigned long OpenSSLInitializer::id() +{ + return vmime::platform::getHandler()->getThreadId(); +} + + +} // tls +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL + diff --git a/src/vmime/net/tls/openssl/OpenSSLInitializer.hpp b/src/vmime/net/tls/openssl/OpenSSLInitializer.hpp new file mode 100644 index 00000000..d7595aa8 --- /dev/null +++ b/src/vmime/net/tls/openssl/OpenSSLInitializer.hpp @@ -0,0 +1,111 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_TLS_OPENSSL_OPENSSLINITIALIZER_HPP_INCLUDED +#define VMIME_NET_TLS_OPENSSL_OPENSSLINITIALIZER_HPP_INCLUDED + + +#ifndef VMIME_BUILDING_DOC + + +#include "vmime/config.hpp" + +#include <vector> + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL + + +#include "vmime/utility/sync/criticalSection.hpp" + + +namespace vmime { +namespace net { +namespace tls { + + +/** Class responsible for setting up OpenSSL + */ +class OpenSSLInitializer +{ +public: + + /** Automatically initialize OpenSSL + */ + class autoInitializer + { + public: + + autoInitializer(); + ~autoInitializer(); + }; + +protected: + + class oneTimeInitializer + { + public: + + oneTimeInitializer(); + ~oneTimeInitializer(); + }; + + + /** Initializes the OpenSSL lib + */ + static void initialize(); + + /** Shutdown the OpenSSL lib + */ + static void uninitialize(); + + + static shared_ptr <vmime::utility::sync::criticalSection> getMutex(); + + enum + { + SEEDSIZE = 256 + }; + + + // OpenSSL multithreading support + static void lock(int mode, int n, const char* file, int line); + static unsigned long id(); + +private: + + static shared_ptr <vmime::utility::sync::criticalSection >* sm_mutexes; +}; + + +} // tls +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL + +#endif // VMIME_BUILDING_DOC + +#endif // VMIME_NET_TLS_OPENSSL_OPENSSLINITIALIZER_HPP_INCLUDED + diff --git a/src/vmime/net/tls/openssl/TLSProperties_OpenSSL.cpp b/src/vmime/net/tls/openssl/TLSProperties_OpenSSL.cpp new file mode 100644 index 00000000..932477df --- /dev/null +++ b/src/vmime/net/tls/openssl/TLSProperties_OpenSSL.cpp @@ -0,0 +1,112 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL + + +#include "vmime/base.hpp" +#include "vmime/net/tls/openssl/TLSProperties_OpenSSL.hpp" + +#include <openssl/ssl.h> +#include <openssl/err.h> + + +namespace vmime { +namespace net { +namespace tls { + + +TLSProperties::TLSProperties() + : m_data(make_shared <TLSProperties_OpenSSL>()) +{ + setCipherSuite(CIPHERSUITE_DEFAULT); +} + + +TLSProperties::TLSProperties(const TLSProperties& props) + : object(), + m_data(make_shared <TLSProperties_OpenSSL>()) +{ + *dynamicCast <TLSProperties_OpenSSL>(m_data) = *dynamicCast <TLSProperties_OpenSSL>(props.m_data); +} + + +void TLSProperties::setCipherSuite(const GenericCipherSuite cipherSuite) +{ + switch (cipherSuite) + { + case CIPHERSUITE_HIGH: + + setCipherSuite("HIGH:!ADH:@STRENGTH"); + break; + + case CIPHERSUITE_MEDIUM: + + setCipherSuite("MEDIUM:!ADH:@STRENGTH"); + break; + + case CIPHERSUITE_LOW: + + setCipherSuite("LOW:!ADH:@STRENGTH"); + break; + + default: + case CIPHERSUITE_DEFAULT: + + setCipherSuite("DEFAULT:!ADH:@STRENGTH"); + break; + } +} + + +void TLSProperties::setCipherSuite(const string& cipherSuite) +{ + dynamicCast <TLSProperties_OpenSSL>(m_data)->cipherSuite = cipherSuite; +} + + +const string TLSProperties::getCipherSuite() const +{ + return dynamicCast <TLSProperties_OpenSSL>(m_data)->cipherSuite; +} + + + +TLSProperties_OpenSSL& TLSProperties_OpenSSL::operator=(const TLSProperties_OpenSSL& other) +{ + cipherSuite = other.cipherSuite; + + return *this; +} + + +} // tls +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL + diff --git a/src/vmime/net/tls/openssl/TLSProperties_OpenSSL.hpp b/src/vmime/net/tls/openssl/TLSProperties_OpenSSL.hpp new file mode 100644 index 00000000..5d2f075a --- /dev/null +++ b/src/vmime/net/tls/openssl/TLSProperties_OpenSSL.hpp @@ -0,0 +1,68 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_TLS_TLSPROPERTIES_OPENSSL_HPP_INCLUDED +#define VMIME_NET_TLS_TLSPROPERTIES_OPENSSL_HPP_INCLUDED + + +#ifndef VMIME_BUILDING_DOC + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL + + +#include "vmime/types.hpp" + +#include "vmime/net/tls/TLSProperties.hpp" + + +namespace vmime { +namespace net { +namespace tls { + + +class TLSProperties_OpenSSL : public object +{ +public: + + TLSProperties_OpenSSL& operator=(const TLSProperties_OpenSSL& other); + + + string cipherSuite; +}; + + +} // tls +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL + +#endif // VMIME_BUILDING_DOC + +#endif // VMIME_NET_TLS_TLSPROPERTIES_OPENSSL_HPP_INCLUDED + diff --git a/src/vmime/net/tls/openssl/TLSSession_OpenSSL.cpp b/src/vmime/net/tls/openssl/TLSSession_OpenSSL.cpp new file mode 100644 index 00000000..cf600a63 --- /dev/null +++ b/src/vmime/net/tls/openssl/TLSSession_OpenSSL.cpp @@ -0,0 +1,135 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL + + +#include "vmime/net/tls/openssl/TLSSession_OpenSSL.hpp" +#include "vmime/net/tls/openssl/TLSProperties_OpenSSL.hpp" +#include "vmime/net/tls/openssl/OpenSSLInitializer.hpp" + +#include "vmime/exception.hpp" + +#include <openssl/ssl.h> +#include <openssl/err.h> + + +namespace vmime { +namespace net { +namespace tls { + + +static OpenSSLInitializer::autoInitializer openSSLInitializer; + + +// static +shared_ptr <TLSSession> TLSSession::create(shared_ptr <security::cert::certificateVerifier> cv, shared_ptr <TLSProperties> props) +{ + return make_shared <TLSSession_OpenSSL>(cv, props); +} + + +TLSSession_OpenSSL::TLSSession_OpenSSL(shared_ptr <vmime::security::cert::certificateVerifier> cv, shared_ptr <TLSProperties> props) + : m_sslctx(0), m_certVerifier(cv), m_props(props) +{ + m_sslctx = SSL_CTX_new(SSLv23_client_method()); + SSL_CTX_set_options(m_sslctx, SSL_OP_ALL | SSL_OP_NO_SSLv2); + SSL_CTX_set_mode(m_sslctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_cipher_list(m_sslctx, m_props->getCipherSuite().c_str()); + SSL_CTX_set_session_cache_mode(m_sslctx, SSL_SESS_CACHE_OFF); +} + + +TLSSession_OpenSSL::TLSSession_OpenSSL(const TLSSession_OpenSSL&) + : TLSSession() +{ + // Not used +} + + +TLSSession_OpenSSL::~TLSSession_OpenSSL() +{ + SSL_CTX_free(m_sslctx); +} + + +shared_ptr <TLSSocket> TLSSession_OpenSSL::getSocket(shared_ptr <socket> sok) +{ + return TLSSocket::wrap(dynamicCast <TLSSession>(shared_from_this()), sok); +} + + +shared_ptr <security::cert::certificateVerifier> TLSSession_OpenSSL::getCertificateVerifier() +{ + return m_certVerifier; +} + + +void TLSSession_OpenSSL::usePrivateKeyFile(const vmime::string& keyfile) +{ + if (SSL_CTX_use_PrivateKey_file(m_sslctx, keyfile.c_str(), SSL_FILETYPE_PEM) != 1) + { + unsigned long errCode = ERR_get_error(); + char buffer[256]; + ERR_error_string_n(errCode, buffer, sizeof(buffer)); + vmime::string sslErr(buffer); + std::ostringstream oss; + oss << "Error loading private key from file " << keyfile; + oss << " - msg: " << sslErr; + throw exceptions::certificate_exception(oss.str()); + } +} + + +void TLSSession_OpenSSL::useCertificateChainFile(const vmime::string& chainFile) +{ + if (SSL_CTX_use_certificate_chain_file(m_sslctx, chainFile.c_str()) != 1) + { + unsigned long errCode = ERR_get_error(); + char buffer[256]; + ERR_error_string_n(errCode, buffer, sizeof(buffer)); + vmime::string sslErr(buffer); + std::ostringstream oss; + oss << "Error loading certificate from file " << chainFile; + oss << " - msg: " << sslErr; + throw exceptions::certificate_exception(oss.str()); + } +} + + +SSL_CTX* TLSSession_OpenSSL::getContext() const +{ + return m_sslctx; +} + + +} // tls +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL + diff --git a/src/vmime/net/tls/openssl/TLSSession_OpenSSL.hpp b/src/vmime/net/tls/openssl/TLSSession_OpenSSL.hpp new file mode 100644 index 00000000..5a2b60a8 --- /dev/null +++ b/src/vmime/net/tls/openssl/TLSSession_OpenSSL.hpp @@ -0,0 +1,108 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_TLS_TLSSESSION_OPENSSL_HPP_INCLUDED +#define VMIME_NET_TLS_TLSSESSION_OPENSSL_HPP_INCLUDED + + +#ifndef VMIME_BUILDING_DOC + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL + + +#include "vmime/types.hpp" + +#include "vmime/net/tls/TLSSession.hpp" +#include "vmime/net/tls/TLSSocket.hpp" +#include "vmime/net/tls/TLSProperties.hpp" + + +#include <openssl/ssl.h> + + +namespace vmime { +namespace net { +namespace tls { + + +class TLSSession_OpenSSL : public TLSSession +{ + friend class TLSSocket_OpenSSL; + +public: + + TLSSession_OpenSSL(const shared_ptr <security::cert::certificateVerifier> cv, shared_ptr <TLSProperties> props); + ~TLSSession_OpenSSL(); + + + shared_ptr <TLSSocket> getSocket(shared_ptr <socket> sok); + + shared_ptr <security::cert::certificateVerifier> getCertificateVerifier(); + + + /** Set the private key to use if server requires a client certificate. + * + * @param keyfile Path to the private key in PEM format + * @param passwd_callback If the private key is stored encrypted the + */ + void usePrivateKeyFile(const vmime::string& keyfile); + + /** Supply the certificate chain to present if requested by + * server. + * + * @param chainFile File in PEM format holding certificate chain + */ + void useCertificateChainFile(const vmime::string& chainFile); + + /** Get a pointer to the SSL_CTX used for this session. + * + * @return the SSL_CTX used for all connections created with this session + */ + SSL_CTX* getContext() const; + +private: + + TLSSession_OpenSSL(const TLSSession_OpenSSL&); + + SSL_CTX* m_sslctx; + + shared_ptr <security::cert::certificateVerifier> m_certVerifier; + shared_ptr <TLSProperties> m_props; +}; + + +} // tls +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL + +#endif // VMIME_BUILDING_DOC + +#endif // VMIME_NET_TLS_TLSSESSION_OPENSSL_HPP_INCLUDED + diff --git a/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.cpp b/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.cpp new file mode 100644 index 00000000..ef6647d6 --- /dev/null +++ b/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.cpp @@ -0,0 +1,601 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL + + +#include <openssl/ssl.h> +#include <openssl/err.h> + +#include "vmime/net/tls/openssl/TLSSocket_OpenSSL.hpp" +#include "vmime/net/tls/openssl/TLSSession_OpenSSL.hpp" +#include "vmime/net/tls/openssl/OpenSSLInitializer.hpp" + +#include "vmime/platform.hpp" + +#include "vmime/security/cert/openssl/X509Certificate_OpenSSL.hpp" + +#include "vmime/utility/stringUtils.hpp" + +#include <vector> +#include <cstring> + + +namespace vmime { +namespace net { +namespace tls { + + +static OpenSSLInitializer::autoInitializer openSSLInitializer; + + +// static +BIO_METHOD TLSSocket_OpenSSL::sm_customBIOMethod = +{ + 100 | BIO_TYPE_SOURCE_SINK, + "vmime::socket glue", + TLSSocket_OpenSSL::bio_write, + TLSSocket_OpenSSL::bio_read, + TLSSocket_OpenSSL::bio_puts, + NULL, // gets + TLSSocket_OpenSSL::bio_ctrl, + TLSSocket_OpenSSL::bio_create, + TLSSocket_OpenSSL::bio_destroy, + 0 +}; + + +// static +shared_ptr <TLSSocket> TLSSocket::wrap(shared_ptr <TLSSession> session, shared_ptr <socket> sok) +{ + return make_shared <TLSSocket_OpenSSL> + (dynamicCast <TLSSession_OpenSSL>(session), sok); +} + + +TLSSocket_OpenSSL::TLSSocket_OpenSSL(shared_ptr <TLSSession_OpenSSL> session, shared_ptr <socket> sok) + : m_session(session), m_wrapped(sok), m_connected(false), m_ssl(0), m_status(0), m_ex(NULL) +{ +} + + +TLSSocket_OpenSSL::~TLSSocket_OpenSSL() +{ + try + { + disconnect(); + + if (m_ssl) + { + SSL_free(m_ssl); + m_ssl = 0; + } + } + catch (...) + { + // Don't throw in destructor + } +} + + +void TLSSocket_OpenSSL::createSSLHandle() +{ + if (m_wrapped->isConnected()) + { + BIO* sockBio = BIO_new(&sm_customBIOMethod); + sockBio->ptr = this; + sockBio->init = 1; + + m_ssl = SSL_new(m_session->getContext()); + + if (!m_ssl) + { + BIO_free(sockBio); + throw exceptions::tls_exception("Cannot create SSL object"); + } + + SSL_set_bio(m_ssl, sockBio, sockBio); + SSL_set_connect_state(m_ssl); + } + else + { + throw exceptions::tls_exception("Unconnected socket error"); + } +} + + +void TLSSocket_OpenSSL::connect(const string& address, const port_t port) +{ + m_wrapped->connect(address, port); + + createSSLHandle(); + + handshake(null); + + m_connected = true; +} + + +void TLSSocket_OpenSSL::disconnect() +{ + if (m_connected) + { + if (m_ssl) + { + // Don't shut down the socket more than once. + int shutdownState = SSL_get_shutdown(m_ssl); + bool shutdownSent = (shutdownState & SSL_SENT_SHUTDOWN) == SSL_SENT_SHUTDOWN; + + if (!shutdownSent) + SSL_shutdown(m_ssl); + } + + m_wrapped->disconnect(); + m_connected = false; + } +} + + +bool TLSSocket_OpenSSL::isConnected() const +{ + return m_wrapped->isConnected() && m_connected; +} + + +size_t TLSSocket_OpenSSL::getBlockSize() const +{ + return 16384; // 16 KB +} + + +const string TLSSocket_OpenSSL::getPeerName() const +{ + return m_wrapped->getPeerName(); +} + + +const string TLSSocket_OpenSSL::getPeerAddress() const +{ + return m_wrapped->getPeerAddress(); +} + + +void TLSSocket_OpenSSL::receive(string& buffer) +{ + const size_t size = receiveRaw(m_buffer, sizeof(m_buffer)); + + if (size != 0) + buffer = utility::stringUtils::makeStringFromBytes(m_buffer, size); + else + buffer.clear(); +} + + +void TLSSocket_OpenSSL::send(const string& buffer) +{ + sendRaw(reinterpret_cast <const byte_t*>(buffer.data()), buffer.length()); +} + + +void TLSSocket_OpenSSL::send(const char* str) +{ + sendRaw(reinterpret_cast <const byte_t*>(str), ::strlen(str)); +} + + +size_t TLSSocket_OpenSSL::receiveRaw(byte_t* buffer, const size_t count) +{ + m_status &= ~STATUS_WOULDBLOCK; + + int rc = SSL_read(m_ssl, buffer, static_cast <int>(count)); + + if (m_ex.get()) + internalThrow(); + + if (rc <= 0) + { + int error = SSL_get_error(m_ssl, rc); + + if (error == SSL_ERROR_WANT_WRITE || error == SSL_ERROR_WANT_READ) + { + m_status |= STATUS_WOULDBLOCK; + return 0; + } + + handleError(rc); + } + + return rc; +} + + +void TLSSocket_OpenSSL::sendRaw(const byte_t* buffer, const size_t count) +{ + sendRawNonBlocking(buffer, count); +} + + +size_t TLSSocket_OpenSSL::sendRawNonBlocking(const byte_t* buffer, const size_t count) +{ + m_status &= ~STATUS_WOULDBLOCK; + + int rc = SSL_write(m_ssl, buffer, static_cast <int>(count)); + + if (m_ex.get()) + internalThrow(); + + if (rc <= 0) + { + int error = SSL_get_error(m_ssl, rc); + + if (error == SSL_ERROR_WANT_WRITE || error == SSL_ERROR_WANT_READ) + { + m_status |= STATUS_WOULDBLOCK; + return 0; + } + + handleError(rc); + } + + return rc; +} + + +void TLSSocket_OpenSSL::handshake(shared_ptr <timeoutHandler> toHandler) +{ + if (toHandler) + toHandler->resetTimeOut(); + + // Start handshaking process + m_toHandler = toHandler; + + if (!m_ssl) + createSSLHandle(); + + try + { + int rc; + + while ((rc = SSL_do_handshake(m_ssl)) <= 0) + { + const int err = SSL_get_error(m_ssl, rc); + + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) + { + // No data available yet + platform::getHandler()->wait(); + } + else + { + handleError(rc); + } + + // Check whether the time-out delay is elapsed + if (m_toHandler && m_toHandler->isTimeOut()) + { + if (!m_toHandler->handleTimeOut()) + throw exceptions::operation_timed_out(); + + m_toHandler->resetTimeOut(); + } + } + } + catch (...) + { + SSL_free(m_ssl); + m_ssl = 0; + m_toHandler = null; + throw; + } + + m_toHandler = null; + + // Verify server's certificate(s) + shared_ptr <security::cert::certificateChain> certs = getPeerCertificates(); + + if (certs == NULL) + throw exceptions::tls_exception("No peer certificate."); + + m_session->getCertificateVerifier()->verify(certs, getPeerName()); + + m_connected = true; +} + + +shared_ptr <security::cert::certificateChain> TLSSocket_OpenSSL::getPeerCertificates() const +{ + STACK_OF(X509)* chain = SSL_get_peer_cert_chain(m_ssl); + + if (chain == NULL) + return null; + + int certCount = sk_X509_num(chain); + + if (certCount == 0) + return null; + + bool error = false; + std::vector <shared_ptr <security::cert::certificate> > certs; + + for (int i = 0; i < certCount && !error; i++) + { + shared_ptr <vmime::security::cert::X509Certificate> cert = + vmime::security::cert::X509Certificate_OpenSSL::importInternal(sk_X509_value(chain, i)); + + if (cert) + certs.push_back(cert); + else + error = true; + } + + if (error) + return null; + + return make_shared <security::cert::certificateChain>(certs); +} + + +void TLSSocket_OpenSSL::internalThrow() +{ + if (m_ex.get()) + throw *m_ex; +} + + +void TLSSocket_OpenSSL::handleError(int rc) +{ + if (rc > 0) return; + + internalThrow(); + + int sslError = SSL_get_error(m_ssl, rc); + long lastError = ERR_get_error(); + + switch (sslError) + { + case SSL_ERROR_ZERO_RETURN: + return; + + case SSL_ERROR_SYSCALL: + { + if (lastError == 0) + { + if (rc == 0) + { + throw exceptions::tls_exception("SSL connection unexpectedly closed"); + } + else + { + vmime::string msg; + std::ostringstream oss(msg); + oss << "The BIO reported an error: " << rc; + oss.flush(); + throw exceptions::tls_exception(oss.str()); + } + } + break; + } + + case SSL_ERROR_WANT_READ: + + BIO_set_retry_read(SSL_get_rbio(m_ssl)); + break; + + case SSL_ERROR_WANT_WRITE: + + BIO_set_retry_write(SSL_get_wbio(m_ssl)); + break; + + // This happens only for BIOs of type BIO_s_connect() or BIO_s_accept() + case SSL_ERROR_WANT_CONNECT: + case SSL_ERROR_WANT_ACCEPT: + // SSL_CTX_set_client_cert_cb related, not used + case SSL_ERROR_WANT_X509_LOOKUP: + case SSL_ERROR_SSL: + default: + + if (lastError == 0) + { + throw exceptions::tls_exception("Unexpected SSL IO error"); + } + else + { + char buffer[256]; + ERR_error_string_n(lastError, buffer, sizeof(buffer)); + vmime::string msg(buffer); + throw exceptions::tls_exception(msg); + } + + break; + } +} + + +unsigned int TLSSocket_OpenSSL::getStatus() const +{ + return m_status; +} + + +// Implementation of custom BIO methods + + +// static +int TLSSocket_OpenSSL::bio_write(BIO* bio, const char* buf, int len) +{ + BIO_clear_retry_flags(bio); + + if (buf == NULL || len <= 0) + return -1; + + TLSSocket_OpenSSL *sok = reinterpret_cast <TLSSocket_OpenSSL*>(bio->ptr); + + if (!bio->init || !sok) + return -1; + + try + { + const size_t n = sok->m_wrapped->sendRawNonBlocking + (reinterpret_cast <const byte_t*>(buf), len); + + if (n == 0 && sok->m_wrapped->getStatus() & socket::STATUS_WOULDBLOCK) + { + BIO_set_retry_write(bio); + return -1; + } + + return static_cast <int>(len); + } + catch (exception& e) + { + // Workaround for passing C++ exceptions from C BIO functions + sok->m_ex.reset(e.clone()); + return -1; + } +} + + +// static +int TLSSocket_OpenSSL::bio_read(BIO* bio, char* buf, int len) +{ + BIO_clear_retry_flags(bio); + + if (buf == NULL || len <= 0) + return -1; + + TLSSocket_OpenSSL *sok = reinterpret_cast <TLSSocket_OpenSSL*>(bio->ptr); + + if (!bio->init || !sok) + return -1; + + try + { + const size_t n = sok->m_wrapped->receiveRaw + (reinterpret_cast <byte_t*>(buf), len); + + if (n == 0 || sok->m_wrapped->getStatus() & socket::STATUS_WOULDBLOCK) + { + BIO_set_retry_read(bio); + return -1; + } + + return static_cast <int>(n); + } + catch (exception& e) + { + // Workaround for passing C++ exceptions from C BIO functions + sok->m_ex.reset(e.clone()); + return -1; + } +} + + +// static +int TLSSocket_OpenSSL::bio_puts(BIO* bio, const char* str) +{ + return bio_write(bio, str, static_cast <int>(strlen(str))); +} + + +// static +long TLSSocket_OpenSSL::bio_ctrl(BIO* bio, int cmd, long num, void* ptr) +{ + long ret = 1; + + switch (cmd) + { + case BIO_CTRL_INFO: + + ret = 0; + break; + + case BIO_CTRL_GET_CLOSE: + + ret = bio->shutdown; + break; + + case BIO_CTRL_SET_CLOSE: + + bio->shutdown = static_cast <int>(num); + break; + + case BIO_CTRL_PENDING: + case BIO_CTRL_WPENDING: + + ret = 0; + break; + + case BIO_CTRL_DUP: + case BIO_CTRL_FLUSH: + + ret = 1; + break; + + default: + + ret = 0; + break; + } + + return ret; +} + + +// static +int TLSSocket_OpenSSL::bio_create(BIO* bio) +{ + bio->init = 0; + bio->num = 0; + bio->ptr = NULL; + bio->flags = 0; + + return 1; +} + + +// static +int TLSSocket_OpenSSL::bio_destroy(BIO* bio) +{ + if (bio == NULL) + return 0; + + if (bio->shutdown) + { + bio->ptr = NULL; + bio->init = 0; + bio->flags = 0; + } + + return 1; +} + + +} // tls +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL diff --git a/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.hpp b/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.hpp new file mode 100644 index 00000000..410fffcf --- /dev/null +++ b/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.hpp @@ -0,0 +1,132 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_TLS_TLSSOCKET_OPENSSL_HPP_INCLUDED +#define VMIME_NET_TLS_TLSSOCKET_OPENSSL_HPP_INCLUDED + + +#ifndef VMIME_BUILDING_DOC + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL + + +#include "vmime/net/tls/TLSSocket.hpp" + +#include <memory> + +#include <openssl/ssl.h> + + +namespace vmime { +namespace net { +namespace tls { + + +class TLSSession; +class TLSSession_OpenSSL; + + +class TLSSocket_OpenSSL : public TLSSocket +{ +public: + + TLSSocket_OpenSSL(shared_ptr <TLSSession_OpenSSL> session, shared_ptr <socket> sok); + ~TLSSocket_OpenSSL(); + + + void handshake(shared_ptr <timeoutHandler> toHandler = null); + + shared_ptr <security::cert::certificateChain> getPeerCertificates() const; + + // Implementation of 'socket' + void connect(const string& address, const port_t port); + void disconnect(); + bool isConnected() const; + + void receive(string& buffer); + size_t receiveRaw(byte_t* buffer, const size_t count); + + void send(const string& buffer); + void send(const char* str); + void sendRaw(const byte_t* buffer, const size_t count); + size_t sendRawNonBlocking(const byte_t* buffer, const size_t count); + + size_t getBlockSize() const; + + unsigned int getStatus() const; + + const string getPeerName() const; + const string getPeerAddress() const; + +private: + + static BIO_METHOD sm_customBIOMethod; + + static int bio_write(BIO* bio, const char* buf, int len); + static int bio_read(BIO* bio, char* buf, int len); + static int bio_puts(BIO* bio, const char* str); + static int bio_gets(BIO* bio, char* buf, int len); + static long bio_ctrl(BIO* bio, int cmd, long num, void* ptr); + static int bio_create(BIO* bio); + static int bio_destroy(BIO* bio); + + void createSSLHandle(); + + void internalThrow(); + void handleError(int rc); + + + shared_ptr <TLSSession_OpenSSL> m_session; + + shared_ptr <socket> m_wrapped; + + bool m_connected; + + byte_t m_buffer[65536]; + + shared_ptr <timeoutHandler> m_toHandler; + + SSL* m_ssl; + + unsigned long m_status; + + // Last exception thrown from C BIO functions + std::auto_ptr <std::exception> m_ex; +}; + + +} // tls +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL + +#endif // VMIME_BUILDING_DOC + +#endif // VMIME_NET_TLS_TLSSOCKET_OPENSSL_HPP_INCLUDED + diff --git a/src/vmime/net/transport.cpp b/src/vmime/net/transport.cpp new file mode 100644 index 00000000..dd7281d0 --- /dev/null +++ b/src/vmime/net/transport.cpp @@ -0,0 +1,236 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#include "vmime/net/transport.hpp" + +#include "vmime/utility/stream.hpp" +#include "vmime/mailboxList.hpp" +#include "vmime/message.hpp" +#include "vmime/dateTime.hpp" +#include "vmime/messageId.hpp" + +#include "vmime/utility/outputStreamAdapter.hpp" +#include "vmime/utility/inputStreamStringAdapter.hpp" + + +namespace vmime { +namespace net { + + +transport::transport(shared_ptr <session> sess, const serviceInfos& infos, shared_ptr <security::authenticator> auth) + : service(sess, infos, auth) +{ +} + + +shared_ptr <headerField> transport::processHeaderField(shared_ptr <headerField> field) +{ + if (utility::stringUtils::isStringEqualNoCase(field->getName(), fields::BCC)) + { + // Remove Bcc headers from the message, as required by the RFC. + // Some SMTP server automatically strip this header (Postfix, qmail), + // and others have an option for this (Exim). + return null; + } + else if (utility::stringUtils::isStringEqualNoCase(field->getName(), fields::RETURN_PATH)) + { + // RFC-2821: Return-Path header is added by the final transport system + // that delivers the message to its recipient. Then, it should not be + // transmitted to MSA. + return null; + } + else if (utility::stringUtils::isStringEqualNoCase(field->getName(), fields::ORIGINAL_RECIPIENT)) + { + // RFC-2298: Delivering MTA may add the Original-Recipient header and + // discard existing one; so, no need to send it. + return null; + } + + // Leave the header field as is + return field; +} + + +void transport::processHeader(shared_ptr <header> header) +{ + if (header->getFieldCount() == 0) + return; + + // Remove/replace fields + for (size_t idx = header->getFieldCount() ; idx != 0 ; --idx) + { + shared_ptr <headerField> field = header->getFieldAt(idx - 1); + shared_ptr <headerField> newField = processHeaderField(field); + + if (newField == NULL) + header->removeField(field); + else if (newField != field) + header->replaceField(field, newField); + } + + // Add missing header fields + // -- Date + if (!header->hasField(fields::DATE)) + header->Date()->setValue(datetime::now()); + + // -- Mime-Version + if (!header->hasField(fields::MIME_VERSION)) + header->MimeVersion()->setValue(string(SUPPORTED_MIME_VERSION)); + + // -- Message-Id + if (!header->hasField(fields::MESSAGE_ID)) + header->MessageId()->setValue(messageId::generateId()); +} + + +static void extractMailboxes + (mailboxList& recipients, const addressList& list) +{ + for (size_t i = 0 ; i < list.getAddressCount() ; ++i) + { + shared_ptr <mailbox> mbox = dynamicCast <mailbox>(list.getAddressAt(i)->clone()); + + if (mbox != NULL) + recipients.appendMailbox(mbox); + } +} + + +void transport::send(shared_ptr <vmime::message> msg, utility::progressListener* progress) +{ + // Extract expeditor + shared_ptr <mailbox> fromMbox = + msg->getHeader()->findFieldValue <mailbox>(fields::FROM); + + if (!fromMbox) + throw exceptions::no_expeditor(); + + mailbox expeditor = *fromMbox; + + // Extract sender + shared_ptr <mailbox> senderMbox = + msg->getHeader()->findFieldValue <mailbox>(fields::SENDER); + + mailbox sender; + + if (!senderMbox) + sender = expeditor; + else + sender = *senderMbox; + + // Extract recipients + mailboxList recipients; + + // -- "To" field + shared_ptr <addressList> addresses = + msg->getHeader()->findFieldValue <addressList>(fields::TO); + + if (addresses) + extractMailboxes(recipients, *addresses); + + // -- "Cc" field + addresses = + msg->getHeader()->findFieldValue <addressList>(fields::CC); + + if (addresses) + extractMailboxes(recipients, *addresses); + + // -- "Bcc" field + addresses = + msg->getHeader()->findFieldValue <addressList>(fields::BCC); + + if (addresses) + extractMailboxes(recipients, *addresses); + + // Process message header by removing fields that should be removed + // before transmitting the message to MSA, and adding missing fields + // which are required/recommended by the RFCs. + shared_ptr <header> hdr = vmime::clone(msg->getHeader()); + processHeader(hdr); + + // To avoid cloning message body (too much overhead), use processed + // header during the time we are generating the message to a stream. + // Revert it back to original header after. + struct XChangeMsgHeader + { + XChangeMsgHeader(shared_ptr <vmime::message> _msg, + shared_ptr <vmime::header> _hdr) + : msg(_msg), hdr(msg->getHeader()) + { + // Set new header + msg->setHeader(_hdr); + } + + ~XChangeMsgHeader() + { + // Revert original header + msg->setHeader(hdr); + } + + private: + + shared_ptr <vmime::message> msg; + shared_ptr <vmime::header> hdr; + } headerExchanger(msg, hdr); + + send(msg, expeditor, recipients, progress, sender); +} + + +void transport::send + (shared_ptr <vmime::message> msg, const mailbox& expeditor, const mailboxList& recipients, + utility::progressListener* progress, const mailbox& sender) +{ + // 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, sender); +} + + +transport::Type transport::getType() const +{ + return (TYPE_TRANSPORT); +} + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + diff --git a/src/vmime/net/transport.hpp b/src/vmime/net/transport.hpp new file mode 100644 index 00000000..6c405cbb --- /dev/null +++ b/src/vmime/net/transport.hpp @@ -0,0 +1,137 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 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 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_TRANSPORT_HPP_INCLUDED +#define VMIME_NET_TRANSPORT_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#include "vmime/net/service.hpp" +#include "vmime/utility/stream.hpp" + +#include "vmime/mailboxList.hpp" + + +namespace vmime { + +class header; +class headerField; +class message; +class mailbox; +class mailboxList; + +namespace net { + + +/** A transport service. + * Encapsulate protocols that can send messages. + */ + +class VMIME_EXPORT transport : public service +{ +protected: + + transport(shared_ptr <session> sess, const serviceInfos& infos, shared_ptr <security::authenticator> auth); + +public: + + /** Send a message over this transport service. + * The default implementation simply generates the whole message into + * a string buffer and "streams" it via a inputStreamStringAdapter. + * + * @param msg message to send + * @param progress progress listener, or NULL if not used + */ + virtual void send(shared_ptr <vmime::message> msg, utility::progressListener* progress = NULL); + + /** Send a message over this transport service. + * + * @param expeditor expeditor mailbox + * @param recipients list of recipient mailboxes + * @param is input stream providing message data (header + body) + * @param size size of the message data + * @param progress progress listener, or NULL if not used + * @param sender envelope sender (if empty, expeditor will be used) + */ + virtual void send + (const mailbox& expeditor, + const mailboxList& recipients, + utility::inputStream& is, + const size_t size, + utility::progressListener* progress = NULL, + const mailbox& sender = mailbox()) = 0; + + /** Send a message over this transport service. + * The default implementation simply generates the whole message into + * a string buffer and "streams" it via a inputStreamStringAdapter. + * + * @param msg message to send + * @param expeditor expeditor mailbox + * @param recipients list of recipient mailboxes + * @param progress progress listener, or NULL if not used + * @param sender envelope sender (if empty, expeditor will be used) + */ + virtual void send + (shared_ptr <vmime::message> msg, + const mailbox& expeditor, + const mailboxList& recipients, + utility::progressListener* progress = NULL, + const mailbox& sender = mailbox()); + + + Type getType() const; + +protected: + + /** Called by processHeader(). + * Decides what to do with the specified header field. + * + * @return NULL if the header should be removed, a reference to a new headerField + * if the field is to be replaced, or a reference to the same headerField + * that was passed if the field should be left as is + */ + shared_ptr <headerField> processHeaderField(shared_ptr <headerField> field); + + /** Prepares the header before transmitting the message. + * Removes headers that should not be present (eg. "Bcc", "Return-Path"), + * or adds missing headers that are required/recommended by the RFCs. + * The header is modified inline. + * + * @param header headers to process + */ + void processHeader(shared_ptr <header> header); +}; + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + +#endif // VMIME_NET_TRANSPORT_HPP_INCLUDED |