From 9dcc78085ad73b91f12e6a1fed2744ddd38b959a Mon Sep 17 00:00:00 2001 From: Vincent Richard Date: Sat, 7 Jan 2006 08:46:20 +0000 Subject: [PATCH] Added SMTPResponse to read and parse SMTP responses. --- SConstruct | 1 + src/net/smtp/SMTPResponse.cpp | 242 +++++++++++++++++++++++++++++++ src/net/smtp/SMTPTransport.cpp | 162 ++++----------------- vmime/net/smtp/SMTPResponse.hpp | 142 ++++++++++++++++++ vmime/net/smtp/SMTPTransport.hpp | 13 +- 5 files changed, 417 insertions(+), 143 deletions(-) create mode 100644 src/net/smtp/SMTPResponse.cpp create mode 100644 vmime/net/smtp/SMTPResponse.hpp diff --git a/SConstruct b/SConstruct index dd7847fb..c0747d93 100644 --- a/SConstruct +++ b/SConstruct @@ -233,6 +233,7 @@ libvmime_messaging_proto_sources = [ [ 'smtp', [ + 'net/smtp/SMTPResponse.cpp', 'net/smtp/SMTPResponse.hpp', 'net/smtp/SMTPServiceInfos.cpp', 'net/smtp/SMTPServiceInfos.hpp', 'net/smtp/SMTPTransport.cpp', 'net/smtp/SMTPTransport.hpp', 'net/smtp/SMTPSTransport.cpp', 'net/smtp/SMTPSTransport.hpp' diff --git a/src/net/smtp/SMTPResponse.cpp b/src/net/smtp/SMTPResponse.cpp new file mode 100644 index 00000000..9f5efe94 --- /dev/null +++ b/src/net/smtp/SMTPResponse.cpp @@ -0,0 +1,242 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 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 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/net/smtp/SMTPResponse.hpp" + +#include "vmime/platformDependant.hpp" +#include "vmime/utility/stringUtils.hpp" + +#include "vmime/net/socket.hpp" +#include "vmime/net/timeoutHandler.hpp" + + +namespace vmime { +namespace net { +namespace smtp { + + +SMTPResponse::SMTPResponse(ref sok, ref toh) + : m_socket(sok), m_timeoutHandler(toh), + m_responseContinues(false) +{ +} + + +SMTPResponse::SMTPResponse(const SMTPResponse&) + : vmime::object() +{ + // Not used +} + + +const int SMTPResponse::getCode() const +{ + const int firstCode = m_lines[0].getCode(); + + for (unsigned int i = 1 ; i < m_lines.size() ; ++i) + { + // All response codes returned must be equal + // or else this in an error... + if (m_lines[i].getCode() != firstCode) + return 0; + } + + return firstCode; +} + + +const string SMTPResponse::getText() const +{ + string text = m_lines[0].getText(); + + for (unsigned int i = 1 ; i < m_lines.size() ; ++i) + { + text += '\n'; + text += m_lines[i].getText(); + } + + return text; +} + + +// static +ref SMTPResponse::readResponse + (ref sok, ref toh) +{ + ref resp = vmime::create (sok, toh); + + resp->readResponse(); + + return resp; +} + + +void SMTPResponse::readResponse() +{ + responseLine line = getNextResponse(); + m_lines.push_back(line); + + while (m_responseContinues) + { + line = getNextResponse(); + m_lines.push_back(line); + } +} + + +const string SMTPResponse::readResponseLine() +{ + string currentBuffer = m_responseBuffer; + + while (true) + { + // Get a line from the response buffer + string::size_type lineEnd = currentBuffer.find_first_of('\n'); + + if (lineEnd != string::npos) + { + const string line(currentBuffer.begin(), currentBuffer.begin() + lineEnd); + + currentBuffer.erase(currentBuffer.begin(), currentBuffer.begin() + lineEnd + 1); + m_responseBuffer = currentBuffer; + + return line; + } + + // Check whether the time-out delay is elapsed + if (m_timeoutHandler && m_timeoutHandler->isTimeOut()) + { + if (!m_timeoutHandler->handleTimeOut()) + throw exceptions::operation_timed_out(); + + m_timeoutHandler->resetTimeOut(); + } + + // Receive data from the socket + string receiveBuffer; + m_socket->receive(receiveBuffer); + + if (receiveBuffer.empty()) // buffer is empty + { + platformDependant::getHandler()->wait(); + continue; + } + + currentBuffer += receiveBuffer; + } +} + + +const SMTPResponse::responseLine SMTPResponse::getNextResponse() +{ + string line = readResponseLine(); + + // Special case where CRLF occurs after response code + if (line.length() < 4) + line = line + '\n' + readResponseLine(); + + const int code = extractResponseCode(line); + string text; + + m_responseContinues = (line.length() >= 4 && line[3] == '-'); + + if (line.length() > 4) + text = utility::stringUtils::trim(line.substr(4)); + else + text = utility::stringUtils::trim(line); + + return responseLine(code, text); +} + + +// static +const int SMTPResponse::extractResponseCode(const string& response) +{ + int code = 0; + + if (response.length() >= 3) + { + code = (response[0] - '0') * 100 + + (response[1] - '0') * 10 + + (response[2] - '0'); + } + + return code; +} + + +const SMTPResponse::responseLine SMTPResponse::getLineAt(const unsigned int pos) const +{ + return m_lines[pos]; +} + + +const unsigned int SMTPResponse::getLineCount() const +{ + return m_lines.size(); +} + + +const SMTPResponse::responseLine SMTPResponse::getLastLine() const +{ + return m_lines[m_lines.size() - 1]; +} + + + +// SMTPResponse::responseLine + +SMTPResponse::responseLine::responseLine(const int code, const string& text) + : m_code(code), m_text(text) +{ +} + + +void SMTPResponse::responseLine::setCode(const int code) +{ + m_code = code; +} + + +const int SMTPResponse::responseLine::getCode() const +{ + return m_code; +} + + +void SMTPResponse::responseLine::setText(const string& text) +{ + m_text = text; +} + + +const string SMTPResponse::responseLine::getText() const +{ + return m_text; +} + + +} // smtp +} // net +} // vmime + diff --git a/src/net/smtp/SMTPTransport.cpp b/src/net/smtp/SMTPTransport.cpp index 3c70e34a..48ea5cdc 100644 --- a/src/net/smtp/SMTPTransport.cpp +++ b/src/net/smtp/SMTPTransport.cpp @@ -18,6 +18,7 @@ // #include "vmime/net/smtp/SMTPTransport.hpp" +#include "vmime/net/smtp/SMTPResponse.hpp" #include "vmime/exception.hpp" #include "vmime/platformDependant.hpp" @@ -110,19 +111,17 @@ void SMTPTransport::connect() m_socket->connect(address, port); - m_responseBuffer.clear(); - // Connection // // eg: C: // --- S: 220 smtp.domain.com Service ready - string response; + ref resp; - if (readAllResponses(response) != 220) + if ((resp = readResponse())->getCode() != 220) { internalDisconnect(); - throw exceptions::connection_greeting_error(response); + throw exceptions::connection_greeting_error(resp->getText()); } // Identification @@ -134,7 +133,7 @@ void SMTPTransport::connect() sendRequest("EHLO " + platformDependant::getHandler()->getHostName()); - if (readAllResponses(response, true) != 250) + if ((resp = readResponse())->getCode() != 250) { // Next, try "Basic" SMTP // @@ -143,10 +142,10 @@ void SMTPTransport::connect() sendRequest("HELO " + platformDependant::getHandler()->getHostName()); - if (readAllResponses(response) != 250) + if ((resp = readResponse())->getCode() != 250) { internalDisconnect(); - throw exceptions::connection_greeting_error(response); + throw exceptions::connection_greeting_error(resp->getLastLine().getText()); } m_extendedSMTP = false; @@ -154,7 +153,7 @@ void SMTPTransport::connect() else { m_extendedSMTP = true; - m_extendedSMTPResponse = response; + m_extendedSMTPResponse = resp->getText(); } #if VMIME_HAVE_TLS_SUPPORT @@ -335,9 +334,9 @@ void SMTPTransport::authenticateSASL() for (bool cont = true ; cont ; ) { - string response; + ref response = readResponse(); - switch (readAllResponses(response)) + switch (response->getCode()) { case 235: { @@ -355,7 +354,7 @@ void SMTPTransport::authenticateSASL() try { // Extract challenge - saslContext->decodeB64(response, &challenge, &challengeLen); + saslContext->decodeB64(response->getText(), &challenge, &challengeLen); // Prepare response saslSession->evaluateChallenge @@ -423,10 +422,10 @@ void SMTPTransport::startTLS() { sendRequest("STARTTLS"); - string response; + ref resp = readResponse(); - if (readAllResponses(response) != 220) - throw exceptions::command_error("STARTTLS", response); + if (resp->getCode() != 220) + throw exceptions::command_error("STARTTLS", resp->getText()); ref tlsSession = vmime::create (getCertificateVerifier()); @@ -494,10 +493,10 @@ void SMTPTransport::noop() { sendRequest("NOOP"); - string response; + ref resp = readResponse(); - if (readAllResponses(response) != 250) - throw exceptions::command_error("NOOP", response); + if (resp->getCode() != 250) + throw exceptions::command_error("NOOP", resp->getText()); } @@ -512,14 +511,14 @@ void SMTPTransport::send(const mailbox& expeditor, const mailboxList& recipients throw exceptions::no_expeditor(); // Emit the "MAIL" command - string response; + ref resp; sendRequest("MAIL FROM: <" + expeditor.getEmail() + ">"); - if (readAllResponses(response) != 250) + if ((resp = readResponse())->getCode() != 250) { internalDisconnect(); - throw exceptions::command_error("MAIL", response); + throw exceptions::command_error("MAIL", resp->getText()); } // Emit a "RCPT TO" command for each recipient @@ -529,20 +528,20 @@ void SMTPTransport::send(const mailbox& expeditor, const mailboxList& recipients sendRequest("RCPT TO: <" + mbox.getEmail() + ">"); - if (readAllResponses(response) != 250) + if ((resp = readResponse())->getCode() != 250) { internalDisconnect(); - throw exceptions::command_error("RCPT TO", response); + throw exceptions::command_error("RCPT TO", resp->getText()); } } // Send the message data sendRequest("DATA"); - if (readAllResponses(response) != 354) + if ((resp = readResponse())->getCode() != 354) { internalDisconnect(); - throw exceptions::command_error("DATA", response); + throw exceptions::command_error("DATA", resp->getText()); } // Stream copy with "\n." to "\n.." transformation @@ -556,10 +555,10 @@ void SMTPTransport::send(const mailbox& expeditor, const mailboxList& recipients // Send end-of-data delimiter m_socket->sendRaw("\r\n.\r\n", 5); - if (readAllResponses(response) != 250) + if ((resp = readResponse())->getCode() != 250) { internalDisconnect(); - throw exceptions::command_error("DATA", response); + throw exceptions::command_error("DATA", resp->getText()); } } @@ -571,114 +570,9 @@ void SMTPTransport::sendRequest(const string& buffer, const bool end) } -const int SMTPTransport::getResponseCode(const string& response) +ref SMTPTransport::readResponse() { - int code = 0; - - if (response.length() >= 3) - { - code = (response[0] - '0') * 100 - + (response[1] - '0') * 10 - + (response[2] - '0'); - } - - return (code); -} - - -const string SMTPTransport::readResponseLine() -{ - string currentBuffer = m_responseBuffer; - - while (true) - { - // Get a line from the response buffer - string::size_type lineEnd = currentBuffer.find_first_of('\n'); - - if (lineEnd != string::npos) - { - const string line(currentBuffer.begin(), currentBuffer.begin() + lineEnd); - - currentBuffer.erase(currentBuffer.begin(), currentBuffer.begin() + lineEnd + 1); - m_responseBuffer = currentBuffer; - - return line; - } - - // Check whether the time-out delay is elapsed - if (m_timeoutHandler && m_timeoutHandler->isTimeOut()) - { - if (!m_timeoutHandler->handleTimeOut()) - throw exceptions::operation_timed_out(); - - m_timeoutHandler->resetTimeOut(); - } - - // Receive data from the socket - string receiveBuffer; - m_socket->receive(receiveBuffer); - - if (receiveBuffer.empty()) // buffer is empty - { - platformDependant::getHandler()->wait(); - continue; - } - - currentBuffer += receiveBuffer; - } -} - - -const int SMTPTransport::readResponse(string& text) -{ - string line = readResponseLine(); - - // Special case where CRLF occurs after response code - if (line.length() < 4) - line = line + '\n' + readResponseLine(); - - const int code = getResponseCode(line); - - m_responseContinues = (line.length() >= 4 && line[3] == '-'); - - if (line.length() > 4) - text = utility::stringUtils::trim(line.substr(4)); - else - text = utility::stringUtils::trim(line); - - return code; -} - - -const int SMTPTransport::readAllResponses(string& outText, const bool allText) -{ - string text; - - const int firstCode = readResponse(outText); - - if (allText) - text = outText; - - while (m_responseContinues) - { - const int code = readResponse(outText); - - if (allText) - text += '\n' + outText; - - if (code != firstCode) - { - if (allText) - outText = text; - - return 0; - } - } - - if (allText) - outText = text; - - return firstCode; + return SMTPResponse::readResponse(m_socket, m_timeoutHandler); } diff --git a/vmime/net/smtp/SMTPResponse.hpp b/vmime/net/smtp/SMTPResponse.hpp new file mode 100644 index 00000000..49681e50 --- /dev/null +++ b/vmime/net/smtp/SMTPResponse.hpp @@ -0,0 +1,142 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 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 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_SMTP_SMTPRESPONSE_HPP_INCLUDED +#define VMIME_NET_SMTP_SMTPRESPONSE_HPP_INCLUDED + + +#include "vmime/object.hpp" +#include "vmime/base.hpp" + + +namespace vmime { +namespace net { + + +class socket; +class timeoutHandler; + + +namespace smtp { + + +/** A SMTP response, as sent by the server. + */ +class SMTPResponse : public object +{ + friend class creator; + +public: + + /** An element of a SMTP response. */ + class responseLine + { + public: + + responseLine(const int code, const string& text); + + void setCode(const int code); + const int getCode() const; + + void setText(const string& text); + const string getText() const; + + private: + + int m_code; + string m_text; + }; + + /** Receive and parse a new SMTP response from the + * specified socket. + * + * @param sok socket from which to read + * @param toh time-out handler + * @return SMTP response + * @throws exceptions::operation_timed_out if no data + * has been received within the granted time + */ + static ref readResponse(ref sok, ref toh); + + /** Return the SMTP response code. + * + * @return response code + */ + const int getCode() const; + + /** Return the SMTP response text. + * The text of each line is concatenated. + * + * @return response text + */ + const string getText() const; + + /** Return the response line at the specified position. + * + * @param pos line index + * @return line at the specified index + */ + const responseLine getLineAt(const unsigned int pos) const; + + /** Return the number of lines in the response. + * + * @return number of lines in the response + */ + const unsigned int getLineCount() const; + + /** Return the last line in the response. + * + * @return last response line + */ + const responseLine getLastLine() const; + +private: + + SMTPResponse(ref sok, ref toh); + SMTPResponse(const SMTPResponse&); + + void readResponse(); + + const string readResponseLine(); + const responseLine getNextResponse(); + + static const int extractResponseCode(const string& response); + + + std::vector m_lines; + + ref m_socket; + ref m_timeoutHandler; + + string m_responseBuffer; + bool m_responseContinues; +}; + + +} // smtp +} // net +} // vmime + + +#endif // VMIME_NET_SMTP_SMTPRESPONSE_HPP_INCLUDED + diff --git a/vmime/net/smtp/SMTPTransport.hpp b/vmime/net/smtp/SMTPTransport.hpp index 96b0bef5..d42288b7 100644 --- a/vmime/net/smtp/SMTPTransport.hpp +++ b/vmime/net/smtp/SMTPTransport.hpp @@ -39,6 +39,9 @@ namespace net { namespace smtp { +class SMTPResponse; + + /** SMTP transport service. */ @@ -64,13 +67,8 @@ public: private: - static const int getResponseCode(const string& response); - void sendRequest(const string& buffer, const bool end = true); - - const string readResponseLine(); - const int readResponse(string& text); - const int readAllResponses(string& text, const bool allText = false); + ref readResponse(); void internalDisconnect(); @@ -89,9 +87,6 @@ private: bool m_extendedSMTP; string m_extendedSMTPResponse; - string m_responseBuffer; - bool m_responseContinues; - ref m_timeoutHandler; bool m_secured;