diff --git a/SConstruct b/SConstruct index 31228ed4..d1bb23c5 100644 --- a/SConstruct +++ b/SConstruct @@ -236,6 +236,7 @@ libvmime_messaging_proto_sources = [ 'net/pop3/POP3SStore.cpp', 'net/pop3/POP3SStore.hpp', 'net/pop3/POP3Folder.cpp', 'net/pop3/POP3Folder.hpp', 'net/pop3/POP3Message.cpp', 'net/pop3/POP3Message.hpp', + 'net/pop3/POP3Response.cpp', 'net/pop3/POP3Response.hpp', 'net/pop3/POP3Utils.cpp', 'net/pop3/POP3Utils.hpp' ] ], diff --git a/src/net/pop3/POP3Folder.cpp b/src/net/pop3/POP3Folder.cpp index f9a4225f..8533712e 100644 --- a/src/net/pop3/POP3Folder.cpp +++ b/src/net/pop3/POP3Folder.cpp @@ -31,6 +31,7 @@ #include "vmime/net/pop3/POP3Store.hpp" #include "vmime/net/pop3/POP3Message.hpp" +#include "vmime/net/pop3/POP3Response.hpp" #include "vmime/net/pop3/POP3Utils.hpp" @@ -131,19 +132,17 @@ void POP3Folder::open(const int mode, bool failIfModeIsNotAvailable) { store->sendRequest("STAT"); - string response; - store->readResponse(response, false); + ref response = + POP3Response::readResponse(store->m_socket, store->m_timeoutHandler); - if (!store->isSuccessResponse(response)) - throw exceptions::command_error("STAT", response); + if (!response->isSuccess()) + throw exceptions::command_error("STAT", response->getFirstLine()); - store->stripResponseCode(response, response); - - std::istringstream iss(response); + std::istringstream iss(response->getText()); iss >> m_messageCount; if (iss.fail()) - throw exceptions::invalid_response("STAT", response); + throw exceptions::invalid_response("STAT", response->getFirstLine()); m_open = true; m_mode = mode; @@ -167,9 +166,7 @@ void POP3Folder::close(const bool expunge) if (!expunge) { store->sendRequest("RSET"); - - string response; - store->readResponse(response, false); + POP3Response::readResponse(store->m_socket, store->m_timeoutHandler); } m_open = false; @@ -371,13 +368,11 @@ void POP3Folder::fetchMessages(std::vector >& msg, const int opti store->sendRequest(command.str()); // Get the response - string response; - store->readResponse(response, true, NULL); + ref response = + POP3Response::readMultilineResponse(store->m_socket, store->m_timeoutHandler); - if (store->isSuccessResponse(response)) + if (response->isSuccess()) { - store->stripFirstLine(response, response, NULL); - // C: LIST // S: +OK // S: 1 47548 @@ -416,13 +411,11 @@ void POP3Folder::fetchMessages(std::vector >& msg, const int opti store->sendRequest(command.str()); // Get the response - string response; - store->readResponse(response, true, NULL); + ref response = + POP3Response::readMultilineResponse(store->m_socket, store->m_timeoutHandler); - if (store->isSuccessResponse(response)) + if (response->isSuccess()) { - store->stripFirstLine(response, response, NULL); - // C: UIDL // S: +OK // S: 1 whqtswO00WBw418f9t5JxYwZ @@ -472,26 +465,26 @@ void POP3Folder::fetchMessage(ref msg, const int options) store->sendRequest(command.str()); // Get the response - string response; - store->readResponse(response, false, NULL); + ref response = + POP3Response::readResponse(store->m_socket, store->m_timeoutHandler); - if (store->isSuccessResponse(response)) + if (response->isSuccess()) { - store->stripResponseCode(response, response); + string responseText = response->getText(); // C: LIST 2 // S: +OK 2 4242 - string::iterator it = response.begin(); + string::iterator it = responseText.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; + 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 != response.end()) + if (it != responseText.end()) { int size = 0; - std::istringstream iss(string(it, response.end())); + std::istringstream iss(string(it, responseText.end())); iss >> size; msg.dynamicCast ()->m_size = size; @@ -510,25 +503,25 @@ void POP3Folder::fetchMessage(ref msg, const int options) store->sendRequest(command.str()); // Get the response - string response; - store->readResponse(response, false, NULL); + ref response = + POP3Response::readResponse(store->m_socket, store->m_timeoutHandler); - if (store->isSuccessResponse(response)) + if (response->isSuccess()) { - store->stripResponseCode(response, response); + string responseText = response->getText(); // C: UIDL 2 // S: +OK 2 QhdPYR:00WBw1Ph7x7 - string::iterator it = response.begin(); + string::iterator it = responseText.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; + 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 != response.end()) + if (it != responseText.end()) { msg.dynamicCast ()->m_uid = - string(it, response.end()); + string(it, responseText.end()); } } } @@ -598,11 +591,11 @@ void POP3Folder::deleteMessage(const int num) store->sendRequest(command.str()); - string response; - store->readResponse(response, false); + ref response = + POP3Response::readResponse(store->m_socket, store->m_timeoutHandler); - if (!store->isSuccessResponse(response)) - throw exceptions::command_error("DELE", response); + if (!response->isSuccess()) + throw exceptions::command_error("DELE", response->getFirstLine()); // Update local flags for (std::map ::iterator it = @@ -649,11 +642,11 @@ void POP3Folder::deleteMessages(const int from, const int to) store->sendRequest(command.str()); - string response; - store->readResponse(response, false); + ref response = + POP3Response::readResponse(store->m_socket, store->m_timeoutHandler); - if (!store->isSuccessResponse(response)) - throw exceptions::command_error("DELE", response); + if (!response->isSuccess()) + throw exceptions::command_error("DELE", response->getFirstLine()); } // Update local flags @@ -702,11 +695,11 @@ void POP3Folder::deleteMessages(const std::vector & nums) store->sendRequest(command.str()); - string response; - store->readResponse(response, false); + ref response = + POP3Response::readResponse(store->m_socket, store->m_timeoutHandler); - if (!store->isSuccessResponse(response)) - throw exceptions::command_error("DELE", response); + if (!response->isSuccess()) + throw exceptions::command_error("DELE", response->getFirstLine()); } // Sort message list @@ -799,15 +792,13 @@ void POP3Folder::status(int& count, int& unseen) store->sendRequest("STAT"); - string response; - store->readResponse(response, false); + ref response = + POP3Response::readResponse(store->m_socket, store->m_timeoutHandler); - if (!store->isSuccessResponse(response)) - throw exceptions::command_error("STAT", response); + if (!response->isSuccess()) + throw exceptions::command_error("STAT", response->getFirstLine()); - store->stripResponseCode(response, response); - - std::istringstream iss(response); + std::istringstream iss(response->getText()); iss >> count; unseen = count; diff --git a/src/net/pop3/POP3Message.cpp b/src/net/pop3/POP3Message.cpp index eb33ddca..59b4c443 100644 --- a/src/net/pop3/POP3Message.cpp +++ b/src/net/pop3/POP3Message.cpp @@ -28,10 +28,12 @@ #include "vmime/net/pop3/POP3Message.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 @@ -140,12 +142,14 @@ void POP3Message::extract(utility::outputStream& os, std::ostringstream oss; oss << "RETR " << m_num; - folder.constCast ()->m_store.acquire()->sendRequest(oss.str()); + ref store = folder.constCast ()->m_store.acquire(); + + store->sendRequest(oss.str()); try { - folder.constCast ()->m_store.acquire()-> - readResponse(os, progress, m_size); + POP3Response::readLargeResponse + (store->m_socket, store->m_timeoutHandler, os, progress, m_size); } catch (exceptions::command_error& e) { @@ -197,12 +201,17 @@ void POP3Message::fetch(ref msgFolder, const int options) std::ostringstream oss; oss << "TOP " << m_num << " 0"; - folder->m_store.acquire()->sendRequest(oss.str()); + ref store = folder->m_store.acquire(); + + store->sendRequest(oss.str()); try { string buffer; - folder->m_store.acquire()->readResponse(buffer, true); + utility::outputStreamStringAdapter bufferStream(buffer); + + POP3Response::readLargeResponse(store->m_socket, store->m_timeoutHandler, + bufferStream, /* progress */ NULL, /* predictedSize */ 0); m_header = vmime::create
(); m_header->parse(buffer); diff --git a/src/net/pop3/POP3Response.cpp b/src/net/pop3/POP3Response.cpp new file mode 100644 index 00000000..ab792611 --- /dev/null +++ b/src/net/pop3/POP3Response.cpp @@ -0,0 +1,426 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2009 Vincent Richard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 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/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(ref sok, ref toh) + : m_socket(sok), m_timeoutHandler(toh) +{ +} + + +// static +ref POP3Response::readResponse + (ref sok, ref toh) +{ + ref resp = vmime::create (sok, toh); + + string buffer; + resp->readResponseImpl(buffer, /* multiLine */ false); + + resp->m_firstLine = buffer; + resp->m_code = getResponseCode(buffer); + stripResponseCode(buffer, resp->m_text); + + return resp; +} + + +// static +ref POP3Response::readMultilineResponse + (ref sok, ref toh) +{ + ref resp = vmime::create (sok, toh); + + 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 +ref POP3Response::readLargeResponse + (ref sok, ref toh, + utility::outputStream& os, utility::progressListener* progress, const long predictedSize) +{ + ref resp = vmime::create (sok, toh); + + 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 unsigned int pos) const +{ + return m_lines[pos]; +} + + +unsigned int 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(); + + string::value_type 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 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 = static_cast ((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 long predictedSize) +{ + long 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 + { + 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) + { + temp.append(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 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 + { + if (firstLine) *firstLine = 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 string::size_type 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/net/pop3/POP3Store.cpp b/src/net/pop3/POP3Store.cpp index 749c5dea..a1dac0a0 100644 --- a/src/net/pop3/POP3Store.cpp +++ b/src/net/pop3/POP3Store.cpp @@ -29,14 +29,12 @@ #include "vmime/net/pop3/POP3Store.hpp" #include "vmime/net/pop3/POP3Folder.hpp" +#include "vmime/net/pop3/POP3Response.hpp" #include "vmime/exception.hpp" #include "vmime/platform.hpp" #include "vmime/messageId.hpp" #include "vmime/security/digest/messageDigestFactory.hpp" -#include "vmime/utility/filteredStream.hpp" -#include "vmime/utility/stringUtils.hpp" -#include "vmime/utility/inputStreamSocketAdapter.hpp" #include "vmime/net/defaultConnectionInfos.hpp" @@ -174,13 +172,12 @@ void POP3Store::connect() // eg: C: // --- S: +OK MailSite POP3 Server 5.3.4.0 Ready <36938848.1056800841.634@somewhere.com> - string response; - readResponse(response, false); + ref response = POP3Response::readResponse(m_socket, m_timeoutHandler); - if (!isSuccessResponse(response)) + if (!response->isSuccess()) { internalDisconnect(); - throw exceptions::connection_greeting_error(response); + throw exceptions::connection_greeting_error(response->getFirstLine()); } #if VMIME_HAVE_TLS_SUPPORT @@ -217,7 +214,7 @@ void POP3Store::connect() #endif // VMIME_HAVE_TLS_SUPPORT // Start authentication process - authenticate(messageId(response)); + authenticate(messageId(response->getText())); } @@ -265,7 +262,7 @@ void POP3Store::authenticate(const messageId& randomMID) const string username = getAuthenticator()->getUsername(); const string password = getAuthenticator()->getPassword(); - string response; + ref response; if (GET_PROPERTY(bool, PROPERTY_OPTIONS_APOP)) { @@ -280,9 +277,9 @@ void POP3Store::authenticate(const messageId& randomMID) md5->finalize(); sendRequest("APOP " + username + " " + md5->getHexDigest()); - readResponse(response, false); + response = POP3Response::readResponse(m_socket, m_timeoutHandler); - if (isSuccessResponse(response)) + if (response->isSuccess()) { m_authentified = true; return; @@ -302,20 +299,19 @@ void POP3Store::authenticate(const messageId& randomMID) { // Can't fallback on basic authentication internalDisconnect(); - throw exceptions::authentication_error(response); + throw exceptions::authentication_error(response->getFirstLine()); } // Ensure connection is valid (cf. note above) try { - string response2; sendRequest("NOOP"); - readResponse(response2, false); + POP3Response::readResponse(m_socket, m_timeoutHandler); } catch (exceptions::socket_exception&) { internalDisconnect(); - throw exceptions::authentication_error(response); + throw exceptions::authentication_error(response->getFirstLine()); } } } @@ -339,21 +335,21 @@ void POP3Store::authenticate(const messageId& randomMID) // C: PASS couic // S: +OK vincent's maildrop has 2 messages (320 octets) sendRequest("USER " + username); - readResponse(response, false); + response = POP3Response::readResponse(m_socket, m_timeoutHandler); - if (!isSuccessResponse(response)) + if (!response->isSuccess()) { internalDisconnect(); - throw exceptions::authentication_error(response); + throw exceptions::authentication_error(response->getFirstLine()); } sendRequest("PASS " + password); - readResponse(response, false); + response = POP3Response::readResponse(m_socket, m_timeoutHandler); - if (!isSuccessResponse(response)) + if (!response->isSuccess()) { internalDisconnect(); - throw exceptions::authentication_error(response); + throw exceptions::authentication_error(response->getFirstLine()); } m_authentified = true; @@ -453,17 +449,16 @@ void POP3Store::authenticateSASL() for (bool cont = true ; cont ; ) { - string response; - readResponse(response, false); + ref response = POP3Response::readResponse(m_socket, m_timeoutHandler); - switch (getResponseCode(response)) + switch (response->getCode()) { - case RESPONSE_OK: + case POP3Response::CODE_OK: { m_socket = saslSession->getSecuredSocket(m_socket); return; } - case RESPONSE_READY: + case POP3Response::CODE_READY: { byte_t* challenge = 0; long challengeLen = 0; @@ -474,8 +469,7 @@ void POP3Store::authenticateSASL() try { // Extract challenge - stripResponseCode(response, response); - saslContext->decodeB64(response, &challenge, &challengeLen); + saslContext->decodeB64(response->getText(), &challenge, &challengeLen); // Prepare response saslSession->evaluateChallenge @@ -543,11 +537,10 @@ void POP3Store::startTLS() { sendRequest("STLS"); - string response; - readResponse(response, false); + ref response = POP3Response::readResponse(m_socket, m_timeoutHandler); - if (getResponseCode(response) != RESPONSE_OK) - throw exceptions::command_error("STLS", response); + if (!response->isSuccess()) + throw exceptions::command_error("STLS", response->getFirstLine()); ref tlsSession = tls::TLSSession::create(getCertificateVerifier()); @@ -641,11 +634,11 @@ void POP3Store::noop() { sendRequest("NOOP"); - string response; - readResponse(response, false); + ref response = + POP3Response::readResponse(m_socket, m_timeoutHandler); - if (!isSuccessResponse(response)) - throw exceptions::command_error("NOOP", response); + if (!response->isSuccess()) + throw exceptions::command_error("NOOP", response->getFirstLine()); } @@ -653,89 +646,21 @@ const std::vector POP3Store::getCapabilities() { sendRequest("CAPA"); - string response; - readResponse(response, true); + ref response = + POP3Response::readMultilineResponse(m_socket, m_timeoutHandler); std::vector res; - if (isSuccessResponse(response)) + if (response->isSuccess()) { - stripFirstLine(response, response); - - std::istringstream iss(response); - string line; - - while (std::getline(iss, line, '\n')) - res.push_back(utility::stringUtils::trim(line)); + for (unsigned int i = 0, n = response->getLineCount() ; i < n ; ++i) + res.push_back(response->getLineAt(i)); } return res; } -bool POP3Store::isSuccessResponse(const string& buffer) -{ - return getResponseCode(buffer) == RESPONSE_OK; -} - - -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); - } -} - - -int POP3Store::getResponseCode(const string& buffer) -{ - if (buffer.length() >= 2) - { - // +[space] - if (buffer[0] == '+' && - (buffer[1] == ' ' || buffer[1] == '\t')) - { - return RESPONSE_READY; - } - - // +OK - if (buffer.length() >= 3) - { - if (buffer[0] == '+' && - (buffer[1] == 'O' || buffer[1] == 'o') && - (buffer[2] == 'K' || buffer[1] == 'k')) - { - return RESPONSE_OK; - } - } - } - - // -ERR or whatever - return RESPONSE_ERR; -} - - -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) @@ -745,233 +670,6 @@ void POP3Store::sendRequest(const string& buffer, const bool end) } -void POP3Store::readResponse(string& buffer, const bool multiLine, - utility::progressListener* progress) -{ - bool foundTerminator = false; - long 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(); - - 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 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 = static_cast ((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 progress - 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::progressListener* progress, const int predictedSize) -{ - long 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 - { - 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) - { - temp.append(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); - } - } - - if (progress) - progress->stop(total); -} - - -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); -} - - -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); diff --git a/src/net/pop3/POP3Utils.cpp b/src/net/pop3/POP3Utils.cpp index f75d338e..c065b695 100644 --- a/src/net/pop3/POP3Utils.cpp +++ b/src/net/pop3/POP3Utils.cpp @@ -28,6 +28,7 @@ #include "vmime/net/pop3/POP3Utils.hpp" +#include "vmime/net/pop3/POP3Response.hpp" #include @@ -38,15 +39,13 @@ namespace pop3 { // static -void POP3Utils::parseMultiListOrUidlResponse(const string& response, std::map & result) +void POP3Utils::parseMultiListOrUidlResponse(ref response, std::map & result) { - std::istringstream iss(response); std::map ids; - string line; - - while (std::getline(iss, line)) + for (unsigned int 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')) diff --git a/vmime/net/pop3/POP3Response.hpp b/vmime/net/pop3/POP3Response.hpp new file mode 100644 index 00000000..b968b577 --- /dev/null +++ b/vmime/net/pop3/POP3Response.hpp @@ -0,0 +1,186 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2009 Vincent Richard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 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" + + +namespace vmime { +namespace net { + + +class socket; +class timeoutHandler; + + +namespace pop3 { + + +/** A POP3 response, as sent by the server. + */ +class POP3Response : public object +{ + friend class vmime::creator; + +public: + + /** Possible response codes. */ + enum ResponseCode + { + CODE_OK = 0, + CODE_READY, + CODE_ERR + }; + + + /** Receive and parse a POP3 response from the + * specified socket. + * + * @param sok socket from which to read + * @param toh time-out handler (can be NULL) + * @return POP3 response + * @throws exceptions::operation_timed_out if no data + * has been received within the granted time + */ + static ref readResponse + (ref sok, ref toh); + + /** Receive and parse a multiline POP3 response from + * the specified socket. + * + * @param sok socket from which to read + * @param toh time-out handler (can be NULL) + * @return POP3 response + * @throws exceptions::operation_timed_out if no data + * has been received within the granted time + */ + static ref readMultilineResponse + (ref sok, ref toh); + + /** Receive and parse a large POP3 response (eg. message data) + * from the specified socket. + * + * @param sok socket from which to read + * @param toh time-out handler (can be NULL) + * @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 ref readLargeResponse + (ref sok, ref toh, + utility::outputStream& os, + utility::progressListener* progress, const long 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 unsigned int pos) const; + + /** Return the number of lines in the response. + * + * @return number of lines in the response + */ + unsigned int getLineCount() const; + +private: + + POP3Response(ref sok, ref toh); + + void readResponseImpl(string& buffer, const bool multiLine); + void readResponseImpl + (string& firstLine, utility::outputStream& os, + utility::progressListener* progress, const long 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); + + + ref m_socket; + ref m_timeoutHandler; + + string m_firstLine; + ResponseCode m_code; + string m_text; + + std::vector 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/vmime/net/pop3/POP3Store.hpp b/vmime/net/pop3/POP3Store.hpp index 816e16c3..af115256 100644 --- a/vmime/net/pop3/POP3Store.hpp +++ b/vmime/net/pop3/POP3Store.hpp @@ -87,13 +87,6 @@ public: private: - enum ResponseCode - { - RESPONSE_OK = 0, - RESPONSE_READY, - RESPONSE_ERR - }; - void authenticate(const messageId& randomMID); #if VMIME_HAVE_SASL_SUPPORT void authenticateSASL(); @@ -105,17 +98,7 @@ private: const std::vector getCapabilities(); - static bool isSuccessResponse(const string& buffer); - static bool stripFirstLine(const string& buffer, string& result, string* firstLine = NULL); - static void stripResponseCode(const string& buffer, string& result); - static int getResponseCode(const string& buffer); - void sendRequest(const string& buffer, const bool end = true); - void readResponse(string& buffer, const bool multiLine, utility::progressListener* progress = NULL); - void readResponse(utility::outputStream& os, utility::progressListener* progress = NULL, const int predictedSize = 0); - - static bool checkTerminator(string& buffer, const bool multiLine); - static bool checkOneTerminator(string& buffer, const string& term); void internalDisconnect(); diff --git a/vmime/net/pop3/POP3Utils.hpp b/vmime/net/pop3/POP3Utils.hpp index 65d5043d..eca053f0 100644 --- a/vmime/net/pop3/POP3Utils.hpp +++ b/vmime/net/pop3/POP3Utils.hpp @@ -41,6 +41,9 @@ namespace net { namespace pop3 { +class POP3Response; + + class POP3Utils { public: @@ -59,7 +62,7 @@ public: * data (either UID or size) */ static void parseMultiListOrUidlResponse - (const string& response, std::map & result); + (ref response, std::map & result); };