SMTP Command Pipelining (RFC-2920).

This commit is contained in:
Vincent Richard 2012-11-11 21:55:44 +01:00
parent f4c42cc0cf
commit 6ae75bc971
4 changed files with 149 additions and 35 deletions

View File

@ -41,9 +41,9 @@ namespace net {
namespace smtp { namespace smtp {
SMTPResponse::SMTPResponse(ref <socket> sok, ref <timeoutHandler> toh) SMTPResponse::SMTPResponse(ref <socket> sok, ref <timeoutHandler> toh, const state& st)
: m_socket(sok), m_timeoutHandler(toh), : 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 // static
ref <SMTPResponse> SMTPResponse::readResponse ref <SMTPResponse> SMTPResponse::readResponse
(ref <socket> sok, ref <timeoutHandler> toh) (ref <socket> sok, ref <timeoutHandler> toh, const state& st)
{ {
ref <SMTPResponse> resp = vmime::create <SMTPResponse>(sok, toh); ref <SMTPResponse> resp = vmime::create <SMTPResponse>(sok, toh, st);
resp->readResponse(); 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 // SMTPResponse::responseLine

View File

@ -68,7 +68,7 @@ namespace smtp {
SMTPTransport::SMTPTransport(ref <session> sess, ref <security::authenticator> auth, const bool secured) SMTPTransport::SMTPTransport(ref <session> sess, ref <security::authenticator> auth, const bool secured)
: transport(sess, getInfosInstance(), auth), m_socket(NULL), : transport(sess, getInfosInstance(), auth), m_socket(NULL),
m_authentified(false), m_extendedSMTP(false), m_timeoutHandler(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()) else if (expeditor.isEmpty())
throw exceptions::no_expeditor(); throw exceptions::no_expeditor();
// Emit the "MAIL" command
ref <SMTPResponse> resp; ref <SMTPResponse> resp;
sendRequest("MAIL FROM:<" + expeditor.getEmail() + ">"); if (m_extensions.find("PIPELINING") != m_extensions.end())
if ((resp = readResponse())->getCode() != 250)
{ {
internalDisconnect(); beginCommandPipeline();
throw exceptions::command_error("MAIL", resp->getText());
// 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());
}
} }
else
// Emit a "RCPT TO" command for each recipient
for (int i = 0 ; i < recipients.getMailboxCount() ; ++i)
{ {
const mailbox& mbox = *recipients.getMailboxAt(i); // Emit the "MAIL" command
sendRequest("MAIL FROM:<" + expeditor.getEmail() + ">");
sendRequest("RCPT TO:<" + mbox.getEmail() + ">");
if ((resp = readResponse())->getCode() != 250) if ((resp = readResponse())->getCode() != 250)
{ {
internalDisconnect(); 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 // 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 // Stream copy with "\n." to "\n.." transformation
utility::outputStreamSocketAdapter sos(*m_socket); utility::outputStreamSocketAdapter sos(*m_socket);
utility::dotFilteredOutputStream fos(sos); 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) void SMTPTransport::sendRequest(const string& buffer, const bool end)
{ {
if (end) if (m_pipelineStarted)
m_socket->send(buffer + "\r\n"); {
m_pipeline << buffer;
if (end)
m_pipeline << "\r\n";
}
else else
m_socket->send(buffer); {
if (end)
m_socket->send(buffer + "\r\n");
else
m_socket->send(buffer);
}
} }
ref <SMTPResponse> SMTPTransport::readResponse() ref <SMTPResponse> SMTPTransport::readResponse()
{ {
return SMTPResponse::readResponse(m_socket, m_timeoutHandler); ref <SMTPResponse> resp = SMTPResponse::readResponse
(m_socket, m_timeoutHandler, m_responseState);
m_responseState = resp->getCurrentState();
return resp;
} }

View File

@ -54,6 +54,12 @@ class SMTPResponse : public object
public: public:
/** Current state of response parser. */
struct state
{
string responseBuffer;
};
/** An element of a SMTP response. */ /** An element of a SMTP response. */
class responseLine class responseLine
{ {
@ -78,11 +84,12 @@ public:
* *
* @param sok socket from which to read * @param sok socket from which to read
* @param toh time-out handler * @param toh time-out handler
* @param st previous state of response parser for the specified socket
* @return SMTP response * @return SMTP response
* @throws exceptions::operation_timed_out if no data * @throws exceptions::operation_timed_out if no data
* has been received within the granted time * has been received within the granted time
*/ */
static ref <SMTPResponse> readResponse(ref <socket> sok, ref <timeoutHandler> toh); static ref <SMTPResponse> readResponse(ref <socket> sok, ref <timeoutHandler> toh, const state& st);
/** Return the SMTP response code. /** Return the SMTP response code.
* *
@ -116,9 +123,15 @@ public:
*/ */
const responseLine getLastLine() const; const responseLine getLastLine() const;
/** Returns the current state of the response parser.
*
* @return current parser state
*/
const state getCurrentState() const;
private: private:
SMTPResponse(ref <socket> sok, ref <timeoutHandler> toh); SMTPResponse(ref <socket> sok, ref <timeoutHandler> toh, const state& st);
SMTPResponse(const SMTPResponse&); SMTPResponse(const SMTPResponse&);
void readResponse(); void readResponse();

View File

@ -36,6 +36,7 @@
#include "vmime/net/timeoutHandler.hpp" #include "vmime/net/timeoutHandler.hpp"
#include "vmime/net/smtp/SMTPServiceInfos.hpp" #include "vmime/net/smtp/SMTPServiceInfos.hpp"
#include "vmime/net/smtp/SMTPResponse.hpp"
namespace vmime { namespace vmime {
@ -43,9 +44,6 @@ namespace net {
namespace smtp { namespace smtp {
class SMTPResponse;
/** SMTP transport service. /** SMTP transport service.
*/ */
@ -102,6 +100,15 @@ private:
bool m_secured; bool m_secured;
ref <connectionInfos> m_cntInfos; ref <connectionInfos> m_cntInfos;
SMTPResponse::state m_responseState;
// Pipelining
std::ostringstream m_pipeline;
bool m_pipelineStarted;
void beginCommandPipeline();
void endCommandPipeline();
// Service infos // Service infos
static SMTPServiceInfos sm_infos; static SMTPServiceInfos sm_infos;