diff options
Diffstat (limited to 'src/net/pop3')
-rw-r--r-- | src/net/pop3/POP3Folder.cpp | 826 | ||||
-rw-r--r-- | src/net/pop3/POP3Message.cpp | 213 | ||||
-rw-r--r-- | src/net/pop3/POP3Store.cpp | 630 |
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 + |