aboutsummaryrefslogtreecommitdiffstats
path: root/src/net/pop3
diff options
context:
space:
mode:
Diffstat (limited to 'src/net/pop3')
-rw-r--r--src/net/pop3/POP3Folder.cpp826
-rw-r--r--src/net/pop3/POP3Message.cpp213
-rw-r--r--src/net/pop3/POP3Store.cpp630
3 files changed, 1669 insertions, 0 deletions
diff --git a/src/net/pop3/POP3Folder.cpp b/src/net/pop3/POP3Folder.cpp
new file mode 100644
index 00000000..807f9b6c
--- /dev/null
+++ b/src/net/pop3/POP3Folder.cpp
@@ -0,0 +1,826 @@
+//
+// VMime library (http://www.vmime.org)
+// Copyright (C) 2002-2005 Vincent Richard <[email protected]>
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+//
+
+#include "vmime/net/pop3/POP3Folder.hpp"
+
+#include "vmime/net/pop3/POP3Store.hpp"
+#include "vmime/net/pop3/POP3Message.hpp"
+
+#include "vmime/exception.hpp"
+
+
+namespace vmime {
+namespace net {
+namespace pop3 {
+
+
+POP3Folder::POP3Folder(const folder::path& path, POP3Store* store)
+ : m_store(store), m_path(path),
+ m_name(path.isEmpty() ? folder::path::component("") : path.getLastComponent()),
+ m_mode(-1), m_open(false)
+{
+ m_store->registerFolder(this);
+}
+
+
+POP3Folder::~POP3Folder()
+{
+ if (m_store)
+ {
+ if (m_open)
+ close(false);
+
+ m_store->unregisterFolder(this);
+ }
+ else if (m_open)
+ {
+ onClose();
+ }
+}
+
+
+const int POP3Folder::getMode() const
+{
+ if (!isOpen())
+ throw exceptions::illegal_state("Folder not open");
+
+ return (m_mode);
+}
+
+
+const int POP3Folder::getType()
+{
+ if (!isOpen())
+ throw exceptions::illegal_state("Folder not open");
+
+ if (m_path.isEmpty())
+ return (TYPE_CONTAINS_FOLDERS);
+ else if (m_path.getSize() == 1 && m_path[0].getBuffer() == "INBOX")
+ return (TYPE_CONTAINS_MESSAGES);
+ else
+ throw exceptions::folder_not_found();
+}
+
+
+const int POP3Folder::getFlags()
+{
+ return (0);
+}
+
+
+const folder::path::component POP3Folder::getName() const
+{
+ return (m_name);
+}
+
+
+const folder::path POP3Folder::getFullPath() const
+{
+ return (m_path);
+}
+
+
+void POP3Folder::open(const int mode, bool failIfModeIsNotAvailable)
+{
+ if (!m_store)
+ throw exceptions::illegal_state("Store disconnected");
+
+ if (m_path.isEmpty())
+ {
+ if (mode != MODE_READ_ONLY && failIfModeIsNotAvailable)
+ throw exceptions::operation_not_supported();
+
+ m_open = true;
+ m_mode = mode;
+
+ m_messageCount = 0;
+ }
+ else if (m_path.getSize() == 1 && m_path[0].getBuffer() == "INBOX")
+ {
+ m_store->sendRequest("STAT");
+
+ string response;
+ m_store->readResponse(response, false);
+
+ if (!m_store->isSuccessResponse(response))
+ throw exceptions::command_error("STAT", response);
+
+ m_store->stripResponseCode(response, response);
+
+ std::istringstream iss(response);
+ iss >> m_messageCount;
+
+ if (iss.fail())
+ throw exceptions::invalid_response("STAT", response);
+
+ m_open = true;
+ m_mode = mode;
+ }
+ else
+ {
+ throw exceptions::folder_not_found();
+ }
+}
+
+void POP3Folder::close(const bool expunge)
+{
+ if (!m_store)
+ throw exceptions::illegal_state("Store disconnected");
+
+ if (!isOpen())
+ throw exceptions::illegal_state("Folder not open");
+
+ if (!expunge)
+ {
+ m_store->sendRequest("RSET");
+
+ string response;
+ m_store->readResponse(response, false);
+ }
+
+ m_open = false;
+ m_mode = -1;
+
+ onClose();
+}
+
+
+void POP3Folder::onClose()
+{
+ for (MessageMap::iterator it = m_messages.begin() ; it != m_messages.end() ; ++it)
+ (*it).first->onFolderClosed();
+
+ m_messages.clear();
+}
+
+
+void POP3Folder::create(const int /* type */)
+{
+ throw exceptions::operation_not_supported();
+}
+
+
+const bool POP3Folder::exists()
+{
+ if (!m_store)
+ throw exceptions::illegal_state("Store disconnected");
+
+ return (m_path.isEmpty() || (m_path.getSize() == 1 && m_path[0].getBuffer() == "INBOX"));
+}
+
+
+const bool POP3Folder::isOpen() const
+{
+ return (m_open);
+}
+
+
+ref <message> POP3Folder::getMessage(const int num)
+{
+ if (!m_store)
+ throw exceptions::illegal_state("Store disconnected");
+ else if (!isOpen())
+ throw exceptions::illegal_state("Folder not open");
+ else if (num < 1 || num > m_messageCount)
+ throw exceptions::message_not_found();
+
+ return vmime::create <POP3Message>(this, num);
+}
+
+
+std::vector <ref <message> > POP3Folder::getMessages(const int from, const int to)
+{
+ const int to2 = (to == -1 ? m_messageCount : to);
+
+ if (!m_store)
+ throw exceptions::illegal_state("Store disconnected");
+ else if (!isOpen())
+ throw exceptions::illegal_state("Folder not open");
+ else if (to2 < from || from < 1 || to2 < 1 || from > m_messageCount || to2 > m_messageCount)
+ throw exceptions::message_not_found();
+
+ std::vector <ref <message> > v;
+
+ for (int i = from ; i <= to2 ; ++i)
+ v.push_back(vmime::create <POP3Message>(this, i));
+
+ return (v);
+}
+
+
+std::vector <ref <message> > POP3Folder::getMessages(const std::vector <int>& nums)
+{
+ if (!m_store)
+ throw exceptions::illegal_state("Store disconnected");
+ else if (!isOpen())
+ throw exceptions::illegal_state("Folder not open");
+
+ std::vector <ref <message> > v;
+
+ for (std::vector <int>::const_iterator it = nums.begin() ; it != nums.end() ; ++it)
+ {
+ if (*it < 1|| *it > m_messageCount)
+ throw exceptions::message_not_found();
+
+ v.push_back(vmime::create <POP3Message>(this, *it));
+ }
+
+ return (v);
+}
+
+
+const int POP3Folder::getMessageCount()
+{
+ if (!m_store)
+ throw exceptions::illegal_state("Store disconnected");
+ else if (!isOpen())
+ throw exceptions::illegal_state("Folder not open");
+
+ return (m_messageCount);
+}
+
+
+ref <folder> POP3Folder::getFolder(const folder::path::component& name)
+{
+ if (!m_store)
+ throw exceptions::illegal_state("Store disconnected");
+
+ return vmime::create <POP3Folder>(m_path / name, m_store);
+}
+
+
+std::vector <ref <folder> > POP3Folder::getFolders(const bool /* recursive */)
+{
+ if (!m_store)
+ throw exceptions::illegal_state("Store disconnected");
+
+ if (m_path.isEmpty())
+ {
+ std::vector <ref <folder> > v;
+ v.push_back(vmime::create <POP3Folder>(folder::path::component("INBOX"), m_store));
+ return (v);
+ }
+ else
+ {
+ std::vector <ref <folder> > v;
+ return (v);
+ }
+}
+
+
+void POP3Folder::fetchMessages(std::vector <ref <message> >& msg, const int options,
+ utility::progressionListener* progress)
+{
+ if (!m_store)
+ throw exceptions::illegal_state("Store disconnected");
+ else if (!isOpen())
+ throw exceptions::illegal_state("Folder not open");
+
+ const int total = msg.size();
+ int current = 0;
+
+ if (progress)
+ progress->start(total);
+
+ for (std::vector <ref <message> >::iterator it = msg.begin() ;
+ it != msg.end() ; ++it)
+ {
+ (*it).dynamicCast <POP3Message>()->fetch(this, options);
+
+ if (progress)
+ progress->progress(++current, total);
+ }
+
+ if (options & FETCH_SIZE)
+ {
+ // Send the "LIST" command
+ std::ostringstream command;
+ command << "LIST";
+
+ m_store->sendRequest(command.str());
+
+ // Get the response
+ string response;
+ m_store->readResponse(response, true, NULL);
+
+ if (m_store->isSuccessResponse(response))
+ {
+ m_store->stripFirstLine(response, response, NULL);
+
+ // C: LIST
+ // S: +OK
+ // S: 1 47548
+ // S: 2 12653
+ // S: .
+ std::map <int, string> result;
+ parseMultiListOrUidlResponse(response, result);
+
+ for (std::vector <ref <message> >::iterator it = msg.begin() ;
+ it != msg.end() ; ++it)
+ {
+ ref <POP3Message> m = (*it).dynamicCast <POP3Message>();
+
+ std::map <int, string>::const_iterator x = result.find(m->m_num);
+
+ if (x != result.end())
+ {
+ int size = 0;
+
+ std::istringstream iss((*x).second);
+ iss >> size;
+
+ m->m_size = size;
+ }
+ }
+ }
+
+ }
+
+ if (options & FETCH_UID)
+ {
+ // Send the "UIDL" command
+ std::ostringstream command;
+ command << "UIDL";
+
+ m_store->sendRequest(command.str());
+
+ // Get the response
+ string response;
+ m_store->readResponse(response, true, NULL);
+
+ if (m_store->isSuccessResponse(response))
+ {
+ m_store->stripFirstLine(response, response, NULL);
+
+ // C: UIDL
+ // S: +OK
+ // S: 1 whqtswO00WBw418f9t5JxYwZ
+ // S: 2 QhdPYR:00WBw1Ph7x7
+ // S: .
+ std::map <int, string> result;
+ parseMultiListOrUidlResponse(response, result);
+
+ for (std::vector <ref <message> >::iterator it = msg.begin() ;
+ it != msg.end() ; ++it)
+ {
+ ref <POP3Message> m = (*it).dynamicCast <POP3Message>();
+
+ std::map <int, string>::const_iterator x = result.find(m->m_num);
+
+ if (x != result.end())
+ m->m_uid = (*x).second;
+ }
+ }
+ }
+
+ if (progress)
+ progress->stop(total);
+}
+
+
+void POP3Folder::fetchMessage(ref <message> msg, const int options)
+{
+ if (!m_store)
+ throw exceptions::illegal_state("Store disconnected");
+ else if (!isOpen())
+ throw exceptions::illegal_state("Folder not open");
+
+ msg.dynamicCast <POP3Message>()->fetch(this, options);
+
+ if (options & FETCH_SIZE)
+ {
+ // Send the "LIST" command
+ std::ostringstream command;
+ command << "LIST " << msg->getNumber();
+
+ m_store->sendRequest(command.str());
+
+ // Get the response
+ string response;
+ m_store->readResponse(response, false, NULL);
+
+ if (m_store->isSuccessResponse(response))
+ {
+ m_store->stripResponseCode(response, response);
+
+ // C: LIST 2
+ // S: +OK 2 4242
+ string::iterator it = response.begin();
+
+ while (it != response.end() && (*it == ' ' || *it == '\t')) ++it;
+ while (it != response.end() && !(*it == ' ' || *it == '\t')) ++it;
+ while (it != response.end() && (*it == ' ' || *it == '\t')) ++it;
+
+ if (it != response.end())
+ {
+ int size = 0;
+
+ std::istringstream iss(string(it, response.end()));
+ iss >> size;
+
+ msg.dynamicCast <POP3Message>()->m_size = size;
+ }
+ }
+ }
+
+ if (options & FETCH_UID)
+ {
+ // Send the "UIDL" command
+ std::ostringstream command;
+ command << "UIDL " << msg->getNumber();
+
+ m_store->sendRequest(command.str());
+
+ // Get the response
+ string response;
+ m_store->readResponse(response, false, NULL);
+
+ if (m_store->isSuccessResponse(response))
+ {
+ m_store->stripResponseCode(response, response);
+
+ // C: UIDL 2
+ // S: +OK 2 QhdPYR:00WBw1Ph7x7
+ string::iterator it = response.begin();
+
+ while (it != response.end() && (*it == ' ' || *it == '\t')) ++it;
+ while (it != response.end() && !(*it == ' ' || *it == '\t')) ++it;
+ while (it != response.end() && (*it == ' ' || *it == '\t')) ++it;
+
+ if (it != response.end())
+ {
+ msg.dynamicCast <POP3Message>()->m_uid =
+ string(it, response.end());
+ }
+ }
+ }
+}
+
+
+const int POP3Folder::getFetchCapabilities() const
+{
+ return (FETCH_ENVELOPE | FETCH_CONTENT_INFO |
+ FETCH_SIZE | FETCH_FULL_HEADER | FETCH_UID |
+ FETCH_IMPORTANCE);
+}
+
+
+ref <folder> POP3Folder::getParent()
+{
+ if (m_path.isEmpty())
+ return NULL;
+ else
+ return vmime::create <POP3Folder>(m_path.getParent(), m_store);
+}
+
+
+weak_ref <const store> POP3Folder::getStore() const
+{
+ return (m_store);
+}
+
+
+weak_ref <store> POP3Folder::getStore()
+{
+ return (m_store);
+}
+
+
+void POP3Folder::registerMessage(POP3Message* msg)
+{
+ m_messages.insert(MessageMap::value_type(msg, msg->getNumber()));
+}
+
+
+void POP3Folder::unregisterMessage(POP3Message* msg)
+{
+ m_messages.erase(msg);
+}
+
+
+void POP3Folder::onStoreDisconnected()
+{
+ m_store = NULL;
+}
+
+
+void POP3Folder::deleteMessage(const int num)
+{
+ if (!m_store)
+ throw exceptions::illegal_state("Store disconnected");
+ else if (!isOpen())
+ throw exceptions::illegal_state("Folder not open");
+
+ std::ostringstream command;
+ command << "DELE " << num;
+
+ m_store->sendRequest(command.str());
+
+ string response;
+ m_store->readResponse(response, false);
+
+ if (!m_store->isSuccessResponse(response))
+ throw exceptions::command_error("DELE", response);
+
+ // Update local flags
+ for (std::map <POP3Message*, int>::iterator it =
+ m_messages.begin() ; it != m_messages.end() ; ++it)
+ {
+ POP3Message* msg = (*it).first;
+
+ if (msg->getNumber() == num)
+ msg->m_deleted = true;
+ }
+
+ // Notify message flags changed
+ std::vector <int> nums;
+ nums.push_back(num);
+
+ events::messageChangedEvent event
+ (thisRef().dynamicCast <folder>(),
+ events::messageChangedEvent::TYPE_FLAGS, nums);
+
+ notifyMessageChanged(event);
+}
+
+
+void POP3Folder::deleteMessages(const int from, const int to)
+{
+ if (from < 1 || (to < from && to != -1))
+ throw exceptions::invalid_argument();
+
+ if (!m_store)
+ throw exceptions::illegal_state("Store disconnected");
+ else if (!isOpen())
+ throw exceptions::illegal_state("Folder not open");
+
+ const int to2 = (to == -1 ? m_messageCount : to);
+
+ for (int i = from ; i <= to2 ; ++i)
+ {
+ std::ostringstream command;
+ command << "DELE " << i;
+
+ m_store->sendRequest(command.str());
+
+ string response;
+ m_store->readResponse(response, false);
+
+ if (!m_store->isSuccessResponse(response))
+ throw exceptions::command_error("DELE", response);
+ }
+
+ // Update local flags
+ for (std::map <POP3Message*, int>::iterator it =
+ m_messages.begin() ; it != m_messages.end() ; ++it)
+ {
+ POP3Message* msg = (*it).first;
+
+ if (msg->getNumber() >= from && msg->getNumber() <= to2)
+ msg->m_deleted = true;
+ }
+
+ // Notify message flags changed
+ std::vector <int> nums;
+
+ for (int i = from ; i <= to2 ; ++i)
+ nums.push_back(i);
+
+ events::messageChangedEvent event
+ (thisRef().dynamicCast <folder>(),
+ events::messageChangedEvent::TYPE_FLAGS, nums);
+
+ notifyMessageChanged(event);
+}
+
+
+void POP3Folder::deleteMessages(const std::vector <int>& nums)
+{
+ if (nums.empty())
+ throw exceptions::invalid_argument();
+
+ if (!m_store)
+ throw exceptions::illegal_state("Store disconnected");
+ else if (!isOpen())
+ throw exceptions::illegal_state("Folder not open");
+
+ for (std::vector <int>::const_iterator
+ it = nums.begin() ; it != nums.end() ; ++it)
+ {
+ std::ostringstream command;
+ command << "DELE " << (*it);
+
+ m_store->sendRequest(command.str());
+
+ string response;
+ m_store->readResponse(response, false);
+
+ if (!m_store->isSuccessResponse(response))
+ throw exceptions::command_error("DELE", response);
+ }
+
+ // Sort message list
+ std::vector <int> list;
+
+ list.resize(nums.size());
+ std::copy(nums.begin(), nums.end(), list.begin());
+
+ std::sort(list.begin(), list.end());
+
+ // Update local flags
+ for (std::map <POP3Message*, int>::iterator it =
+ m_messages.begin() ; it != m_messages.end() ; ++it)
+ {
+ POP3Message* msg = (*it).first;
+
+ if (std::binary_search(list.begin(), list.end(), msg->getNumber()))
+ msg->m_deleted = true;
+ }
+
+ // Notify message flags changed
+ events::messageChangedEvent event
+ (thisRef().dynamicCast <folder>(),
+ events::messageChangedEvent::TYPE_FLAGS, list);
+
+ notifyMessageChanged(event);
+}
+
+
+void POP3Folder::setMessageFlags(const int /* from */, const int /* to */,
+ const int /* flags */, const int /* mode */)
+{
+ throw exceptions::operation_not_supported();
+}
+
+
+void POP3Folder::setMessageFlags(const std::vector <int>& /* nums */,
+ const int /* flags */, const int /* mode */)
+{
+ throw exceptions::operation_not_supported();
+}
+
+
+void POP3Folder::rename(const folder::path& /* newPath */)
+{
+ throw exceptions::operation_not_supported();
+}
+
+
+void POP3Folder::addMessage(ref <vmime::message> /* msg */, const int /* flags */,
+ vmime::datetime* /* date */, utility::progressionListener* /* progress */)
+{
+ throw exceptions::operation_not_supported();
+}
+
+
+void POP3Folder::addMessage(utility::inputStream& /* is */, const int /* size */, const int /* flags */,
+ vmime::datetime* /* date */, utility::progressionListener* /* progress */)
+{
+ throw exceptions::operation_not_supported();
+}
+
+
+void POP3Folder::copyMessage(const folder::path& /* dest */, const int /* num */)
+{
+ throw exceptions::operation_not_supported();
+}
+
+
+void POP3Folder::copyMessages(const folder::path& /* dest */, const int /* from */, const int /* to */)
+{
+ throw exceptions::operation_not_supported();
+}
+
+
+void POP3Folder::copyMessages(const folder::path& /* dest */, const std::vector <int>& /* nums */)
+{
+ throw exceptions::operation_not_supported();
+}
+
+
+void POP3Folder::status(int& count, int& unseen)
+{
+ if (!m_store)
+ throw exceptions::illegal_state("Store disconnected");
+ else if (!isOpen())
+ throw exceptions::illegal_state("Folder not open");
+
+ m_store->sendRequest("STAT");
+
+ string response;
+ m_store->readResponse(response, false);
+
+ if (!m_store->isSuccessResponse(response))
+ throw exceptions::command_error("STAT", response);
+
+ m_store->stripResponseCode(response, response);
+
+ std::istringstream iss(response);
+ iss >> count;
+
+ unseen = count;
+
+ // Update local message count
+ if (m_messageCount != count)
+ {
+ const int oldCount = m_messageCount;
+
+ m_messageCount = count;
+
+ if (count > oldCount)
+ {
+ std::vector <int> nums;
+ nums.reserve(count - oldCount);
+
+ for (int i = oldCount + 1, j = 0 ; i <= count ; ++i, ++j)
+ nums[j] = i;
+
+ // Notify message count changed
+ events::messageCountEvent event
+ (thisRef().dynamicCast <folder>(),
+ events::messageCountEvent::TYPE_ADDED, nums);
+
+ notifyMessageCount(event);
+
+ // Notify folders with the same path
+ for (std::list <POP3Folder*>::iterator it = m_store->m_folders.begin() ;
+ it != m_store->m_folders.end() ; ++it)
+ {
+ if ((*it) != this && (*it)->getFullPath() == m_path)
+ {
+ (*it)->m_messageCount = count;
+
+ events::messageCountEvent event
+ ((*it)->thisRef().dynamicCast <folder>(),
+ events::messageCountEvent::TYPE_ADDED, nums);
+
+ (*it)->notifyMessageCount(event);
+ }
+ }
+ }
+ }
+}
+
+
+void POP3Folder::expunge()
+{
+ // Not supported by POP3 protocol (deleted messages are automatically
+ // expunged at the end of the session...).
+}
+
+
+void POP3Folder::parseMultiListOrUidlResponse(const string& response, std::map <int, string>& result)
+{
+ std::istringstream iss(response);
+ std::map <int, string> ids;
+
+ string line;
+
+ while (std::getline(iss, line))
+ {
+ string::iterator it = line.begin();
+
+ while (it != line.end() && (*it == ' ' || *it == '\t'))
+ ++it;
+
+ if (it != line.end())
+ {
+ int number = 0;
+
+ while (it != line.end() && (*it >= '0' && *it <= '9'))
+ {
+ number = (number * 10) + (*it - '0');
+ ++it;
+ }
+
+ while (it != line.end() && !(*it == ' ' || *it == '\t')) ++it;
+ while (it != line.end() && (*it == ' ' || *it == '\t')) ++it;
+
+ if (it != line.end())
+ {
+ result.insert(std::map <int, string>::value_type(number, string(it, line.end())));
+ }
+ }
+ }
+}
+
+
+} // pop3
+} // net
+} // vmime
diff --git a/src/net/pop3/POP3Message.cpp b/src/net/pop3/POP3Message.cpp
new file mode 100644
index 00000000..b2e2fae4
--- /dev/null
+++ b/src/net/pop3/POP3Message.cpp
@@ -0,0 +1,213 @@
+//
+// VMime library (http://www.vmime.org)
+// Copyright (C) 2002-2005 Vincent Richard <[email protected]>
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+//
+
+#include "vmime/net/pop3/POP3Message.hpp"
+#include "vmime/net/pop3/POP3Folder.hpp"
+#include "vmime/net/pop3/POP3Store.hpp"
+
+#include <sstream>
+
+
+namespace vmime {
+namespace net {
+namespace pop3 {
+
+
+POP3Message::POP3Message(POP3Folder* folder, const int num)
+ : m_folder(folder), m_num(num), m_size(-1), m_deleted(false)
+{
+ m_folder->registerMessage(this);
+}
+
+
+POP3Message::~POP3Message()
+{
+ if (m_folder)
+ m_folder->unregisterMessage(this);
+}
+
+
+void POP3Message::onFolderClosed()
+{
+ m_folder = NULL;
+}
+
+
+const int POP3Message::getNumber() const
+{
+ return (m_num);
+}
+
+
+const message::uid POP3Message::getUniqueId() const
+{
+ return (m_uid);
+}
+
+
+const int POP3Message::getSize() const
+{
+ if (m_size == -1)
+ throw exceptions::unfetched_object();
+
+ return (m_size);
+}
+
+
+const bool POP3Message::isExpunged() const
+{
+ return (false);
+}
+
+
+const int POP3Message::getFlags() const
+{
+ int flags = FLAG_RECENT;
+
+ if (m_deleted)
+ flags |= FLAG_DELETED;
+
+ return (flags);
+}
+
+
+const structure& POP3Message::getStructure() const
+{
+ throw exceptions::operation_not_supported();
+}
+
+
+structure& POP3Message::getStructure()
+{
+ throw exceptions::operation_not_supported();
+}
+
+
+ref <const header> POP3Message::getHeader() const
+{
+ if (m_header == NULL)
+ throw exceptions::unfetched_object();
+
+ return (m_header);
+}
+
+
+void POP3Message::extract(utility::outputStream& os,
+ utility::progressionListener* progress, const int start,
+ const int length, const bool /* peek */) const
+{
+ if (!m_folder)
+ throw exceptions::illegal_state("Folder closed");
+ else if (!m_folder->m_store)
+ throw exceptions::illegal_state("Store disconnected");
+
+ if (start != 0 && length != -1)
+ throw exceptions::partial_fetch_not_supported();
+
+ // Emit the "RETR" command
+ std::ostringstream oss;
+ oss << "RETR " << m_num;
+
+ const_cast <POP3Folder*>(m_folder)->m_store->sendRequest(oss.str());
+
+ try
+ {
+ POP3Folder::MessageMap::const_iterator it =
+ m_folder->m_messages.find(const_cast <POP3Message*>(this));
+
+ const int totalSize = (it != m_folder->m_messages.end())
+ ? (*it).second : 0;
+
+ const_cast <POP3Folder*>(m_folder)->m_store->
+ readResponse(os, progress, totalSize);
+ }
+ catch (exceptions::command_error& e)
+ {
+ throw exceptions::command_error("RETR", e.response());
+ }
+}
+
+
+void POP3Message::extractPart
+ (const part& /* p */, utility::outputStream& /* os */,
+ utility::progressionListener* /* progress */,
+ const int /* start */, const int /* length */,
+ const bool /* peek */) const
+{
+ throw exceptions::operation_not_supported();
+}
+
+
+void POP3Message::fetchPartHeader(part& /* p */)
+{
+ throw exceptions::operation_not_supported();
+}
+
+
+void POP3Message::fetch(POP3Folder* folder, const int options)
+{
+ if (m_folder != folder)
+ throw exceptions::folder_not_found();
+
+ // FETCH_STRUCTURE and FETCH_FLAGS are not supported by POP3.
+ if (options & (folder::FETCH_STRUCTURE | folder::FETCH_FLAGS))
+ throw exceptions::operation_not_supported();
+
+ // Check for the real need to fetch the full header
+ static const int optionsRequiringHeader =
+ folder::FETCH_ENVELOPE | folder::FETCH_CONTENT_INFO |
+ folder::FETCH_FULL_HEADER | folder::FETCH_IMPORTANCE;
+
+ if (!(options & optionsRequiringHeader))
+ return;
+
+ // No need to differenciate between FETCH_ENVELOPE,
+ // FETCH_CONTENT_INFO, ... since POP3 only permits to
+ // retrieve the whole header and not fields in particular.
+
+ // Emit the "TOP" command
+ std::ostringstream oss;
+ oss << "TOP " << m_num << " 0";
+
+ m_folder->m_store->sendRequest(oss.str());
+
+ try
+ {
+ string buffer;
+ m_folder->m_store->readResponse(buffer, true);
+
+ m_header = vmime::create <header>();
+ m_header->parse(buffer);
+ }
+ catch (exceptions::command_error& e)
+ {
+ throw exceptions::command_error("TOP", e.response());
+ }
+}
+
+
+void POP3Message::setFlags(const int /* flags */, const int /* mode */)
+{
+ throw exceptions::operation_not_supported();
+}
+
+
+} // pop3
+} // net
+} // vmime
diff --git a/src/net/pop3/POP3Store.cpp b/src/net/pop3/POP3Store.cpp
new file mode 100644
index 00000000..9ccfa7b7
--- /dev/null
+++ b/src/net/pop3/POP3Store.cpp
@@ -0,0 +1,630 @@
+//
+// VMime library (http://www.vmime.org)
+// Copyright (C) 2002-2005 Vincent Richard <[email protected]>
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+//
+
+#include "vmime/net/pop3/POP3Store.hpp"
+#include "vmime/net/pop3/POP3Folder.hpp"
+
+#include "vmime/exception.hpp"
+#include "vmime/platformDependant.hpp"
+#include "vmime/messageId.hpp"
+#include "vmime/utility/md5.hpp"
+#include "vmime/utility/filteredStream.hpp"
+
+#include <algorithm>
+
+
+// Helpers for service properties
+#define GET_PROPERTY(type, prop) \
+ (sm_infos.getPropertyValue <type>(getSession(), sm_infos.getProperties().prop))
+#define HAS_PROPERTY(prop) \
+ (sm_infos.hasProperty(getSession(), sm_infos.getProperties().prop))
+
+
+namespace vmime {
+namespace net {
+namespace pop3 {
+
+
+POP3Store::POP3Store(ref <session> sess, ref <authenticator> auth)
+ : store(sess, getInfosInstance(), auth), m_socket(NULL),
+ m_authentified(false), m_timeoutHandler(NULL)
+{
+}
+
+
+POP3Store::~POP3Store()
+{
+ if (isConnected())
+ disconnect();
+ else if (m_socket)
+ internalDisconnect();
+}
+
+
+const string POP3Store::getProtocolName() const
+{
+ return "pop3";
+}
+
+
+ref <folder> POP3Store::getDefaultFolder()
+{
+ if (!isConnected())
+ throw exceptions::illegal_state("Not connected");
+
+ return vmime::create <POP3Folder>(folder::path(folder::path::component("INBOX")), this);
+}
+
+
+ref <folder> POP3Store::getRootFolder()
+{
+ if (!isConnected())
+ throw exceptions::illegal_state("Not connected");
+
+ return vmime::create <POP3Folder>(folder::path(), this);
+}
+
+
+ref <folder> POP3Store::getFolder(const folder::path& path)
+{
+ if (!isConnected())
+ throw exceptions::illegal_state("Not connected");
+
+ return vmime::create <POP3Folder>(path, this);
+}
+
+
+const bool POP3Store::isValidFolderName(const folder::path::component& /* name */) const
+{
+ return true;
+}
+
+
+void POP3Store::connect()
+{
+ if (isConnected())
+ throw exceptions::already_connected();
+
+ const string address = GET_PROPERTY(string, PROPERTY_SERVER_ADDRESS);
+ const port_t port = GET_PROPERTY(port_t, PROPERTY_SERVER_PORT);
+
+ // Create the time-out handler
+ if (HAS_PROPERTY(PROPERTY_TIMEOUT_FACTORY))
+ {
+ timeoutHandlerFactory* tof = platformDependant::getHandler()->
+ getTimeoutHandlerFactory(GET_PROPERTY(string, PROPERTY_TIMEOUT_FACTORY));
+
+ m_timeoutHandler = tof->create();
+ }
+
+ // Create and connect the socket
+ socketFactory* sf = platformDependant::getHandler()->
+ getSocketFactory(GET_PROPERTY(string, PROPERTY_SERVER_SOCKETFACTORY));
+
+ m_socket = sf->create();
+ m_socket->connect(address, port);
+
+ // Connection
+ //
+ // eg: C: <connection to server>
+ // --- S: +OK MailSite POP3 Server 5.3.4.0 Ready <[email protected]>
+
+ string response;
+ readResponse(response, false);
+
+ if (isSuccessResponse(response))
+ {
+ bool authentified = false;
+
+ const authenticationInfos auth = getAuthenticator()->requestAuthInfos();
+
+ // Secured authentication with APOP (if requested and if available)
+ //
+ // eg: C: APOP vincent <digest>
+ // --- S: +OK vincent is a valid mailbox
+ messageId mid(response);
+
+ if (GET_PROPERTY(bool, PROPERTY_OPTIONS_APOP))
+ {
+ if (mid.getLeft().length() && mid.getRight().length())
+ {
+ // <digest> is the result of MD5 applied to "<message-id>password"
+ sendRequest("APOP " + auth.getUsername() + " "
+ + utility::md5(mid.generate() + auth.getPassword()).hex());
+ readResponse(response, false);
+
+ if (isSuccessResponse(response))
+ {
+ authentified = true;
+ }
+ else
+ {
+ if (!GET_PROPERTY(bool, PROPERTY_OPTIONS_APOP_FALLBACK))
+ {
+ internalDisconnect();
+ throw exceptions::authentication_error(response);
+ }
+ }
+ }
+ else
+ {
+ // APOP not supported
+ if (!GET_PROPERTY(bool, PROPERTY_OPTIONS_APOP_FALLBACK))
+ {
+ // Can't fallback on basic authentification
+ internalDisconnect();
+ throw exceptions::unsupported_option();
+ }
+ }
+ }
+
+ if (!authentified)
+ {
+ // Basic authentication
+ //
+ // eg: C: USER vincent
+ // --- S: +OK vincent is a valid mailbox
+ //
+ // C: PASS couic
+ // S: +OK vincent's maildrop has 2 messages (320 octets)
+
+ sendRequest("USER " + auth.getUsername());
+ readResponse(response, false);
+
+ if (isSuccessResponse(response))
+ {
+ sendRequest("PASS " + auth.getPassword());
+ readResponse(response, false);
+
+ if (!isSuccessResponse(response))
+ {
+ internalDisconnect();
+ throw exceptions::authentication_error(response);
+ }
+ }
+ else
+ {
+ internalDisconnect();
+ throw exceptions::authentication_error(response);
+ }
+ }
+ }
+ else
+ {
+ internalDisconnect();
+ throw exceptions::connection_greeting_error(response);
+ }
+
+ m_authentified = true;
+}
+
+
+const bool POP3Store::isConnected() const
+{
+ return (m_socket && m_socket->isConnected() && m_authentified);
+}
+
+
+void POP3Store::disconnect()
+{
+ if (!isConnected())
+ throw exceptions::not_connected();
+
+ internalDisconnect();
+}
+
+
+void POP3Store::internalDisconnect()
+{
+ for (std::list <POP3Folder*>::iterator it = m_folders.begin() ;
+ it != m_folders.end() ; ++it)
+ {
+ (*it)->onStoreDisconnected();
+ }
+
+ m_folders.clear();
+
+
+ sendRequest("QUIT");
+
+ m_socket->disconnect();
+ m_socket = NULL;
+
+ m_timeoutHandler = NULL;
+
+ m_authentified = false;
+}
+
+
+void POP3Store::noop()
+{
+ m_socket->send("NOOP");
+
+ string response;
+ readResponse(response, false);
+
+ if (!isSuccessResponse(response))
+ throw exceptions::command_error("NOOP", response);
+}
+
+
+const bool POP3Store::isSuccessResponse(const string& buffer)
+{
+ static const string OK("+OK");
+
+ return (buffer.length() >= 3 &&
+ std::equal(buffer.begin(), buffer.begin() + 3, OK.begin()));
+}
+
+
+const bool POP3Store::stripFirstLine(const string& buffer, string& result, string* firstLine)
+{
+ const string::size_type end = buffer.find('\n');
+
+ if (end != string::npos)
+ {
+ if (firstLine) *firstLine = buffer.substr(0, end);
+ result = buffer.substr(end + 1);
+ return (true);
+ }
+ else
+ {
+ result = buffer;
+ return (false);
+ }
+}
+
+
+void POP3Store::stripResponseCode(const string& buffer, string& result)
+{
+ const string::size_type pos = buffer.find_first_of(" \t");
+
+ if (pos != string::npos)
+ result = buffer.substr(pos + 1);
+ else
+ result = buffer;
+}
+
+
+void POP3Store::sendRequest(const string& buffer, const bool end)
+{
+ if (end)
+ m_socket->send(buffer + "\r\n");
+ else
+ m_socket->send(buffer);
+}
+
+
+void POP3Store::readResponse(string& buffer, const bool multiLine,
+ utility::progressionListener* progress)
+{
+ bool foundTerminator = false;
+ int current = 0, total = 0;
+
+ if (progress)
+ progress->start(total);
+
+ if (m_timeoutHandler)
+ m_timeoutHandler->resetTimeOut();
+
+ buffer.clear();
+
+ string::value_type last1 = '\0', last2 = '\0';
+
+ for ( ; !foundTerminator ; )
+ {
+#if 0 // not supported
+ // Check for possible cancellation
+ if (progress && progress->cancel())
+ throw exceptions::operation_cancelled();
+#endif
+
+ // Check whether the time-out delay is elapsed
+ if (m_timeoutHandler && m_timeoutHandler->isTimeOut())
+ {
+ if (!m_timeoutHandler->handleTimeOut())
+ throw exceptions::operation_timed_out();
+ }
+
+ // Receive data from the socket
+ string receiveBuffer;
+ m_socket->receive(receiveBuffer);
+
+ if (receiveBuffer.empty()) // buffer is empty
+ {
+ platformDependant::getHandler()->wait();
+ continue;
+ }
+
+ // We have received data: reset the time-out counter
+ if (m_timeoutHandler)
+ m_timeoutHandler->resetTimeOut();
+
+ // Check for transparent characters: '\n..' becomes '\n.'
+ const string::value_type first = receiveBuffer[0];
+
+ if (first == '.' && last2 == '\n' && last1 == '.')
+ {
+ receiveBuffer.erase(receiveBuffer.begin());
+ }
+ else if (receiveBuffer.length() >= 2 && first == '.' &&
+ receiveBuffer[1] == '.' && last1 == '\n')
+ {
+ receiveBuffer.erase(receiveBuffer.begin());
+ }
+
+ for (string::size_type trans ;
+ string::npos != (trans = receiveBuffer.find("\n..")) ; )
+ {
+ receiveBuffer.replace(trans, 3, "\n.");
+ }
+
+ last1 = receiveBuffer[receiveBuffer.length() - 1];
+ last2 = (receiveBuffer.length() >= 2) ? receiveBuffer[receiveBuffer.length() - 2] : 0;
+
+ // Append the data to the response buffer
+ buffer += receiveBuffer;
+ current += receiveBuffer.length();
+
+ // Check for terminator string (and strip it if present)
+ foundTerminator = checkTerminator(buffer, multiLine);
+
+ // Notify progression
+ if (progress)
+ {
+ total = std::max(total, current);
+ progress->progress(current, total);
+ }
+
+ // If there is an error (-ERR) when executing a command that
+ // requires a multi-line response, the error response will
+ // include only one line, so we stop waiting for a multi-line
+ // terminator and check for a "normal" one.
+ if (multiLine && !foundTerminator && buffer.length() >= 4 && buffer[0] == '-')
+ {
+ foundTerminator = checkTerminator(buffer, false);
+ }
+ }
+
+ if (progress)
+ progress->stop(total);
+}
+
+
+void POP3Store::readResponse(utility::outputStream& os,
+ utility::progressionListener* progress, const int predictedSize)
+{
+ int current = 0, total = predictedSize;
+
+ string temp;
+ bool codeDone = false;
+
+ if (progress)
+ progress->start(total);
+
+ if (m_timeoutHandler)
+ m_timeoutHandler->resetTimeOut();
+
+ utility::inputStreamSocketAdapter sis(*m_socket);
+ utility::stopSequenceFilteredInputStream <5> sfis1(sis, "\r\n.\r\n");
+ utility::stopSequenceFilteredInputStream <3> sfis2(sfis1, "\n.\n");
+ utility::dotFilteredInputStream dfis(sfis2); // "\n.." --> "\n."
+
+ utility::inputStream& is = dfis;
+
+ while (!is.eof())
+ {
+#if 0 // not supported
+ // Check for possible cancellation
+ if (progress && progress->cancel())
+ throw exceptions::operation_cancelled();
+#endif
+
+ // Check whether the time-out delay is elapsed
+ if (m_timeoutHandler && m_timeoutHandler->isTimeOut())
+ {
+ if (!m_timeoutHandler->handleTimeOut())
+ throw exceptions::operation_timed_out();
+ }
+
+ // Receive data from the socket
+ utility::stream::value_type buffer[65536];
+ const utility::stream::size_type read = is.read(buffer, sizeof(buffer));
+
+ if (read == 0) // buffer is empty
+ {
+ platformDependant::getHandler()->wait();
+ continue;
+ }
+
+ // We have received data: reset the time-out counter
+ if (m_timeoutHandler)
+ m_timeoutHandler->resetTimeOut();
+
+ // If we don't have extracted the response code yet
+ if (!codeDone)
+ {
+ temp += string(buffer, read);
+
+ string firstLine;
+
+ if (stripFirstLine(temp, temp, &firstLine) == true)
+ {
+ if (!isSuccessResponse(firstLine))
+ throw exceptions::command_error("?", firstLine);
+
+ codeDone = true;
+
+ os.write(temp.data(), temp.length());
+ temp.clear();
+
+ continue;
+ }
+ }
+ else
+ {
+ // Inject the data into the output stream
+ os.write(buffer, read);
+ current += read;
+
+ // Notify progression
+ if (progress)
+ {
+ total = std::max(total, current);
+ progress->progress(current, total);
+ }
+ }
+ }
+
+ if (progress)
+ progress->stop(total);
+}
+
+
+const bool POP3Store::checkTerminator(string& buffer, const bool multiLine)
+{
+ // Multi-line response
+ if (multiLine)
+ {
+ static const string term1("\r\n.\r\n");
+ static const string term2("\n.\n");
+
+ return (checkOneTerminator(buffer, term1) ||
+ checkOneTerminator(buffer, term2));
+ }
+ // Normal response
+ else
+ {
+ static const string term1("\r\n");
+ static const string term2("\n");
+
+ return (checkOneTerminator(buffer, term1) ||
+ checkOneTerminator(buffer, term2));
+ }
+
+ return (false);
+}
+
+
+const bool POP3Store::checkOneTerminator(string& buffer, const string& term)
+{
+ if (buffer.length() >= term.length() &&
+ std::equal(buffer.end() - term.length(), buffer.end(), term.begin()))
+ {
+ buffer.erase(buffer.end() - term.length(), buffer.end());
+ return (true);
+ }
+
+ return (false);
+}
+
+
+void POP3Store::registerFolder(POP3Folder* folder)
+{
+ m_folders.push_back(folder);
+}
+
+
+void POP3Store::unregisterFolder(POP3Folder* folder)
+{
+ std::list <POP3Folder*>::iterator it = std::find(m_folders.begin(), m_folders.end(), folder);
+ if (it != m_folders.end()) m_folders.erase(it);
+}
+
+
+const int POP3Store::getCapabilities() const
+{
+ return (CAPABILITY_DELETE_MESSAGE);
+}
+
+
+
+// Service infos
+
+POP3Store::_infos POP3Store::sm_infos;
+
+
+const serviceInfos& POP3Store::getInfosInstance()
+{
+ return (sm_infos);
+}
+
+
+const serviceInfos& POP3Store::getInfos() const
+{
+ return (sm_infos);
+}
+
+
+const string POP3Store::_infos::getPropertyPrefix() const
+{
+ return "store.pop3.";
+}
+
+
+const POP3Store::_infos::props& POP3Store::_infos::getProperties() const
+{
+ static props p =
+ {
+ // POP3-specific options
+ property("options.apop", serviceInfos::property::TYPE_BOOL, "false"),
+ property("options.apop.fallback", serviceInfos::property::TYPE_BOOL, "false"),
+
+ // Common properties
+ property(serviceInfos::property::AUTH_USERNAME, serviceInfos::property::FLAG_REQUIRED),
+ property(serviceInfos::property::AUTH_PASSWORD, serviceInfos::property::FLAG_REQUIRED),
+
+ property(serviceInfos::property::SERVER_ADDRESS, serviceInfos::property::FLAG_REQUIRED),
+ property(serviceInfos::property::SERVER_PORT, "110"),
+ property(serviceInfos::property::SERVER_SOCKETFACTORY),
+
+ property(serviceInfos::property::TIMEOUT_FACTORY)
+ };
+
+ return p;
+}
+
+
+const std::vector <serviceInfos::property> POP3Store::_infos::getAvailableProperties() const
+{
+ std::vector <property> list;
+ const props& p = getProperties();
+
+ // POP3-specific options
+ list.push_back(p.PROPERTY_OPTIONS_APOP);
+ list.push_back(p.PROPERTY_OPTIONS_APOP_FALLBACK);
+
+ // Common properties
+ list.push_back(p.PROPERTY_AUTH_USERNAME);
+ list.push_back(p.PROPERTY_AUTH_PASSWORD);
+
+ list.push_back(p.PROPERTY_SERVER_ADDRESS);
+ list.push_back(p.PROPERTY_SERVER_PORT);
+ list.push_back(p.PROPERTY_SERVER_SOCKETFACTORY);
+
+ list.push_back(p.PROPERTY_TIMEOUT_FACTORY);
+
+ return (list);
+}
+
+
+} // pop3
+} // net
+} // vmime
+