From 6ae75bc9714d8a5cae48b7a2dfe435fd2a56a6da Mon Sep 17 00:00:00 2001 From: Vincent Richard Date: Sun, 11 Nov 2012 21:55:44 +0100 Subject: [PATCH] SMTP Command Pipelining (RFC-2920). --- src/net/smtp/SMTPResponse.cpp | 17 +++- src/net/smtp/SMTPTransport.cpp | 137 +++++++++++++++++++++++++------ vmime/net/smtp/SMTPResponse.hpp | 17 +++- vmime/net/smtp/SMTPTransport.hpp | 13 ++- 4 files changed, 149 insertions(+), 35 deletions(-) diff --git a/src/net/smtp/SMTPResponse.cpp b/src/net/smtp/SMTPResponse.cpp index e1ac2af6..28a1ea87 100644 --- a/src/net/smtp/SMTPResponse.cpp +++ b/src/net/smtp/SMTPResponse.cpp @@ -41,9 +41,9 @@ namespace net { namespace smtp { -SMTPResponse::SMTPResponse(ref sok, ref toh) +SMTPResponse::SMTPResponse(ref sok, ref toh, const state& st) : m_socket(sok), m_timeoutHandler(toh), - m_responseContinues(false) + m_responseBuffer(st.responseBuffer), m_responseContinues(false) { } @@ -87,9 +87,9 @@ const string SMTPResponse::getText() const // static ref SMTPResponse::readResponse - (ref sok, ref toh) + (ref sok, ref toh, const state& st) { - ref resp = vmime::create (sok, toh); + ref resp = vmime::create (sok, toh, st); resp->readResponse(); @@ -218,6 +218,15 @@ const SMTPResponse::responseLine SMTPResponse::getLastLine() const } +const SMTPResponse::state SMTPResponse::getCurrentState() const +{ + state st; + st.responseBuffer = m_responseBuffer; + + return st; +} + + // SMTPResponse::responseLine diff --git a/src/net/smtp/SMTPTransport.cpp b/src/net/smtp/SMTPTransport.cpp index e658ecb7..eb29e65d 100644 --- a/src/net/smtp/SMTPTransport.cpp +++ b/src/net/smtp/SMTPTransport.cpp @@ -68,7 +68,7 @@ namespace smtp { SMTPTransport::SMTPTransport(ref sess, ref auth, const bool secured) : transport(sess, getInfosInstance(), auth), m_socket(NULL), m_authentified(false), m_extendedSMTP(false), m_timeoutHandler(NULL), - m_isSMTPS(secured), m_secured(false) + m_isSMTPS(secured), m_secured(false), m_pipelineStarted(false) { } @@ -571,40 +571,91 @@ void SMTPTransport::send(const mailbox& expeditor, const mailboxList& recipients else if (expeditor.isEmpty()) throw exceptions::no_expeditor(); - // Emit the "MAIL" command + ref resp; - sendRequest("MAIL FROM:<" + expeditor.getEmail() + ">"); - - if ((resp = readResponse())->getCode() != 250) + if (m_extensions.find("PIPELINING") != m_extensions.end()) { - internalDisconnect(); - throw exceptions::command_error("MAIL", resp->getText()); + beginCommandPipeline(); + + // Emit the "MAIL" command + sendRequest("MAIL FROM:<" + expeditor.getEmail() + ">"); + + // Emit a "RCPT TO" command for each recipient + for (int i = 0 ; i < recipients.getMailboxCount() ; ++i) + { + const mailbox& mbox = *recipients.getMailboxAt(i); + + sendRequest("RCPT TO:<" + mbox.getEmail() + ">"); + } + + // Prepare sending of message data + sendRequest("DATA"); + + endCommandPipeline(); + + // Read response for "MAIL" command + if ((resp = readResponse())->getCode() != 250) + { + internalDisconnect(); + throw exceptions::command_error("MAIL", resp->getText()); + } + + // Read responses for "RCPT TO" commands + for (int i = 0 ; i < recipients.getMailboxCount() ; ++i) + { + const mailbox& mbox = *recipients.getMailboxAt(i); + + if ((resp = readResponse())->getCode() != 250) + { + internalDisconnect(); + throw exceptions::command_error("RCPT TO", resp->getText(), mbox.getEmail()); + } + } + + // Read response for "DATA" command + if ((resp = readResponse())->getCode() != 354) + { + internalDisconnect(); + throw exceptions::command_error("DATA", resp->getText()); + } } - - // Emit a "RCPT TO" command for each recipient - for (int i = 0 ; i < recipients.getMailboxCount() ; ++i) + else { - const mailbox& mbox = *recipients.getMailboxAt(i); - - sendRequest("RCPT TO:<" + mbox.getEmail() + ">"); + // Emit the "MAIL" command + sendRequest("MAIL FROM:<" + expeditor.getEmail() + ">"); if ((resp = readResponse())->getCode() != 250) { internalDisconnect(); - throw exceptions::command_error("RCPT TO", resp->getText(), mbox.getEmail()); + throw exceptions::command_error("MAIL", resp->getText()); + } + + // Emit a "RCPT TO" command for each recipient + for (int i = 0 ; i < recipients.getMailboxCount() ; ++i) + { + const mailbox& mbox = *recipients.getMailboxAt(i); + + sendRequest("RCPT TO:<" + mbox.getEmail() + ">"); + + if ((resp = readResponse())->getCode() != 250) + { + internalDisconnect(); + throw exceptions::command_error("RCPT TO", resp->getText(), mbox.getEmail()); + } + } + + // Prepare sending of message data + sendRequest("DATA"); + + if ((resp = readResponse())->getCode() != 354) + { + internalDisconnect(); + throw exceptions::command_error("DATA", resp->getText()); } } // Send the message data - sendRequest("DATA"); - - if ((resp = readResponse())->getCode() != 354) - { - internalDisconnect(); - throw exceptions::command_error("DATA", resp->getText()); - } - // Stream copy with "\n." to "\n.." transformation utility::outputStreamSocketAdapter sos(*m_socket); utility::dotFilteredOutputStream fos(sos); @@ -624,18 +675,52 @@ void SMTPTransport::send(const mailbox& expeditor, const mailboxList& recipients } +void SMTPTransport::beginCommandPipeline() +{ + m_pipeline.clear(); + m_pipelineStarted = true; +} + + +void SMTPTransport::endCommandPipeline() +{ + if (m_pipelineStarted) + { + m_socket->send(m_pipeline.str()); + + m_pipeline.clear(); + m_pipelineStarted = false; + } +} + + void SMTPTransport::sendRequest(const string& buffer, const bool end) { - if (end) - m_socket->send(buffer + "\r\n"); + if (m_pipelineStarted) + { + m_pipeline << buffer; + + if (end) + m_pipeline << "\r\n"; + } else - m_socket->send(buffer); + { + if (end) + m_socket->send(buffer + "\r\n"); + else + m_socket->send(buffer); + } } ref SMTPTransport::readResponse() { - return SMTPResponse::readResponse(m_socket, m_timeoutHandler); + ref resp = SMTPResponse::readResponse + (m_socket, m_timeoutHandler, m_responseState); + + m_responseState = resp->getCurrentState(); + + return resp; } diff --git a/vmime/net/smtp/SMTPResponse.hpp b/vmime/net/smtp/SMTPResponse.hpp index 265e16fe..0a96530c 100644 --- a/vmime/net/smtp/SMTPResponse.hpp +++ b/vmime/net/smtp/SMTPResponse.hpp @@ -54,6 +54,12 @@ class SMTPResponse : public object public: + /** Current state of response parser. */ + struct state + { + string responseBuffer; + }; + /** An element of a SMTP response. */ class responseLine { @@ -78,11 +84,12 @@ public: * * @param sok socket from which to read * @param toh time-out handler + * @param st previous state of response parser for the specified socket * @return SMTP response * @throws exceptions::operation_timed_out if no data * has been received within the granted time */ - static ref readResponse(ref sok, ref toh); + static ref readResponse(ref sok, ref toh, const state& st); /** Return the SMTP response code. * @@ -116,9 +123,15 @@ public: */ const responseLine getLastLine() const; + /** Returns the current state of the response parser. + * + * @return current parser state + */ + const state getCurrentState() const; + private: - SMTPResponse(ref sok, ref toh); + SMTPResponse(ref sok, ref toh, const state& st); SMTPResponse(const SMTPResponse&); void readResponse(); diff --git a/vmime/net/smtp/SMTPTransport.hpp b/vmime/net/smtp/SMTPTransport.hpp index a8b4558b..5cec7c70 100644 --- a/vmime/net/smtp/SMTPTransport.hpp +++ b/vmime/net/smtp/SMTPTransport.hpp @@ -36,6 +36,7 @@ #include "vmime/net/timeoutHandler.hpp" #include "vmime/net/smtp/SMTPServiceInfos.hpp" +#include "vmime/net/smtp/SMTPResponse.hpp" namespace vmime { @@ -43,9 +44,6 @@ namespace net { namespace smtp { -class SMTPResponse; - - /** SMTP transport service. */ @@ -102,6 +100,15 @@ private: bool m_secured; ref m_cntInfos; + SMTPResponse::state m_responseState; + + // Pipelining + std::ostringstream m_pipeline; + bool m_pipelineStarted; + + void beginCommandPipeline(); + void endCommandPipeline(); + // Service infos static SMTPServiceInfos sm_infos;