Added support for CHUNKING SMTP extension (RFC-3030). More robust unit tests for SMTP.
This commit is contained in:
parent
36773bd834
commit
eee80fc81e
@ -252,6 +252,7 @@ libvmime_messaging_proto_sources = [
|
||||
[
|
||||
'smtp',
|
||||
[
|
||||
'net/smtp/SMTPChunkingOutputStreamAdapter.cpp', 'net/smtp/SMTPChunkingOutputStreamAdapter.hpp',
|
||||
'net/smtp/SMTPCommand.cpp', 'net/smtp/SMTPCommand.hpp',
|
||||
'net/smtp/SMTPCommandSet.cpp', 'net/smtp/SMTPCommandSet.hpp',
|
||||
'net/smtp/SMTPConnection.cpp', 'net/smtp/SMTPConnection.hpp',
|
||||
|
145
src/net/smtp/SMTPChunkingOutputStreamAdapter.cpp
Normal file
145
src/net/smtp/SMTPChunkingOutputStreamAdapter.cpp
Normal file
@ -0,0 +1,145 @@
|
||||
//
|
||||
// VMime library (http://www.vmime.org)
|
||||
// Copyright (C) 2002-2013 Vincent Richard <vincent@vmime.org>
|
||||
//
|
||||
// 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_SMTP
|
||||
|
||||
|
||||
#include "vmime/net/smtp/SMTPChunkingOutputStreamAdapter.hpp"
|
||||
|
||||
#include "vmime/net/smtp/SMTPConnection.hpp"
|
||||
#include "vmime/net/smtp/SMTPTransport.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
namespace vmime {
|
||||
namespace net {
|
||||
namespace smtp {
|
||||
|
||||
|
||||
SMTPChunkingOutputStreamAdapter::SMTPChunkingOutputStreamAdapter(ref <SMTPConnection> conn)
|
||||
: m_connection(conn), m_bufferSize(0), m_chunkCount(0)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void SMTPChunkingOutputStreamAdapter::sendChunk
|
||||
(const value_type* const data, const size_type count, const bool last)
|
||||
{
|
||||
if (count == 0 && !last)
|
||||
{
|
||||
// Nothing to send
|
||||
return;
|
||||
}
|
||||
|
||||
// Send this chunk
|
||||
m_connection->sendRequest(SMTPCommand::BDAT(count, last));
|
||||
m_connection->getSocket()->sendRaw(data, count);
|
||||
|
||||
++m_chunkCount;
|
||||
|
||||
// If PIPELINING is not supported, read one response for this BDAT command
|
||||
if (!m_connection->hasExtension("PIPELINING"))
|
||||
{
|
||||
ref <SMTPResponse> resp = m_connection->readResponse();
|
||||
|
||||
if (resp->getCode() != 250)
|
||||
{
|
||||
m_connection->getTransport()->disconnect();
|
||||
throw exceptions::command_error("BDAT", resp->getText());
|
||||
}
|
||||
}
|
||||
// If PIPELINING is supported, read one response for each chunk (ie. number
|
||||
// of BDAT commands issued) after the last chunk has been sent
|
||||
else if (last)
|
||||
{
|
||||
bool invalidReply = false;
|
||||
ref <SMTPResponse> resp;
|
||||
|
||||
for (unsigned int i = 0 ; i < m_chunkCount ; ++i)
|
||||
{
|
||||
resp = m_connection->readResponse();
|
||||
|
||||
if (resp->getCode() != 250)
|
||||
invalidReply = true;
|
||||
}
|
||||
|
||||
if (invalidReply)
|
||||
{
|
||||
m_connection->getTransport()->disconnect();
|
||||
throw exceptions::command_error("BDAT", resp->getText());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SMTPChunkingOutputStreamAdapter::write
|
||||
(const value_type* const data, const size_type count)
|
||||
{
|
||||
const value_type* curData = data;
|
||||
size_type curCount = count;
|
||||
|
||||
while (curCount != 0)
|
||||
{
|
||||
// Fill the buffer
|
||||
const size_type remaining = sizeof(m_buffer) - m_bufferSize;
|
||||
const size_type bytesToCopy = std::min(remaining, curCount);
|
||||
|
||||
std::copy(data, data + bytesToCopy, m_buffer);
|
||||
|
||||
m_bufferSize += bytesToCopy;
|
||||
curData += bytesToCopy;
|
||||
curCount -= bytesToCopy;
|
||||
|
||||
// If the buffer is full, send this chunk
|
||||
if (m_bufferSize >= sizeof(m_buffer))
|
||||
{
|
||||
sendChunk(m_buffer, m_bufferSize, /* last */ false);
|
||||
m_bufferSize = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SMTPChunkingOutputStreamAdapter::flush()
|
||||
{
|
||||
sendChunk(m_buffer, m_bufferSize, /* last */ true);
|
||||
m_bufferSize = 0;
|
||||
}
|
||||
|
||||
|
||||
utility::stream::size_type SMTPChunkingOutputStreamAdapter::getBlockSize()
|
||||
{
|
||||
return sizeof(m_buffer);
|
||||
}
|
||||
|
||||
|
||||
} // smtp
|
||||
} // net
|
||||
} // vmime
|
||||
|
||||
|
||||
#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP
|
@ -149,6 +149,20 @@ ref <SMTPCommand> SMTPCommand::DATA()
|
||||
}
|
||||
|
||||
|
||||
// static
|
||||
ref <SMTPCommand> SMTPCommand::BDAT(const unsigned long chunkSize, const bool last)
|
||||
{
|
||||
std::ostringstream cmd;
|
||||
cmd.imbue(std::locale::classic());
|
||||
cmd << "BDAT " << chunkSize;
|
||||
|
||||
if (last)
|
||||
cmd << " LAST";
|
||||
|
||||
return createCommand(cmd.str());
|
||||
}
|
||||
|
||||
|
||||
// static
|
||||
ref <SMTPCommand> SMTPCommand::NOOP()
|
||||
{
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include "vmime/net/smtp/SMTPResponse.hpp"
|
||||
#include "vmime/net/smtp/SMTPCommand.hpp"
|
||||
#include "vmime/net/smtp/SMTPCommandSet.hpp"
|
||||
#include "vmime/net/smtp/SMTPChunkingOutputStreamAdapter.hpp"
|
||||
|
||||
#include "vmime/exception.hpp"
|
||||
#include "vmime/mailboxList.hpp"
|
||||
@ -155,14 +156,10 @@ void SMTPTransport::noop()
|
||||
}
|
||||
|
||||
|
||||
void SMTPTransport::send
|
||||
void SMTPTransport::sendEnvelope
|
||||
(const mailbox& expeditor, const mailboxList& recipients,
|
||||
utility::inputStream& is, const utility::stream::size_type size,
|
||||
utility::progressListener* progress, const mailbox& sender)
|
||||
const mailbox& sender, bool sendDATACommand)
|
||||
{
|
||||
if (!isConnected())
|
||||
throw exceptions::not_connected();
|
||||
|
||||
// If no recipient/expeditor was found, throw an exception
|
||||
if (recipients.isEmpty())
|
||||
throw exceptions::no_recipient();
|
||||
@ -199,7 +196,8 @@ void SMTPTransport::send
|
||||
}
|
||||
|
||||
// Prepare sending of message data
|
||||
commands->addCommand(SMTPCommand::DATA());
|
||||
if (sendDATACommand)
|
||||
commands->addCommand(SMTPCommand::DATA());
|
||||
|
||||
// Read response for "RSET" command
|
||||
if (needReset)
|
||||
@ -238,13 +236,29 @@ void SMTPTransport::send
|
||||
}
|
||||
|
||||
// Read response for "DATA" command
|
||||
commands->writeToSocket(m_connection->getSocket());
|
||||
|
||||
if ((resp = m_connection->readResponse())->getCode() != 354)
|
||||
if (sendDATACommand)
|
||||
{
|
||||
disconnect();
|
||||
throw exceptions::command_error(commands->getLastCommandSent()->getText(), resp->getText());
|
||||
commands->writeToSocket(m_connection->getSocket());
|
||||
|
||||
if ((resp = m_connection->readResponse())->getCode() != 354)
|
||||
{
|
||||
disconnect();
|
||||
throw exceptions::command_error(commands->getLastCommandSent()->getText(), resp->getText());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SMTPTransport::send
|
||||
(const mailbox& expeditor, const mailboxList& recipients,
|
||||
utility::inputStream& is, const utility::stream::size_type size,
|
||||
utility::progressListener* progress, const mailbox& sender)
|
||||
{
|
||||
if (!isConnected())
|
||||
throw exceptions::not_connected();
|
||||
|
||||
// Send message envelope
|
||||
sendEnvelope(expeditor, recipients, sender, /* sendDATACommand */ true);
|
||||
|
||||
// Send the message data
|
||||
// Stream copy with "\n." to "\n.." transformation
|
||||
@ -258,6 +272,8 @@ void SMTPTransport::send
|
||||
// Send end-of-data delimiter
|
||||
m_connection->getSocket()->sendRaw("\r\n.\r\n", 5);
|
||||
|
||||
ref <SMTPResponse> resp;
|
||||
|
||||
if ((resp = m_connection->readResponse())->getCode() != 250)
|
||||
{
|
||||
disconnect();
|
||||
@ -270,21 +286,39 @@ void SMTPTransport::send
|
||||
(ref <vmime::message> msg, const mailbox& expeditor, const mailboxList& recipients,
|
||||
utility::progressListener* progress, const mailbox& sender)
|
||||
{
|
||||
if (!isConnected())
|
||||
throw exceptions::not_connected();
|
||||
|
||||
// Generate the message with Internationalized Email support,
|
||||
// if this is supported by the SMTP server
|
||||
std::ostringstream oss;
|
||||
utility::outputStreamAdapter ossAdapter(oss);
|
||||
|
||||
generationContext ctx(generationContext::getDefaultContext());
|
||||
ctx.setInternationalizedEmailSupport(m_connection->hasExtension("SMTPUTF8"));
|
||||
|
||||
msg->generate(ctx, ossAdapter);
|
||||
// If CHUNKING is not supported, generate the message to a temporary
|
||||
// buffer then use the send() method which takes an inputStream
|
||||
if (!m_connection->hasExtension("CHUNKING"))
|
||||
{
|
||||
std::ostringstream oss;
|
||||
utility::outputStreamAdapter ossAdapter(oss);
|
||||
|
||||
const string& str(oss.str());
|
||||
msg->generate(ctx, ossAdapter);
|
||||
|
||||
utility::inputStreamStringAdapter isAdapter(str);
|
||||
const string& str(oss.str());
|
||||
|
||||
send(expeditor, recipients, isAdapter, str.length(), progress, sender);
|
||||
utility::inputStreamStringAdapter isAdapter(str);
|
||||
|
||||
send(expeditor, recipients, isAdapter, str.length(), progress, sender);
|
||||
}
|
||||
|
||||
// Send message envelope
|
||||
sendEnvelope(expeditor, recipients, sender, /* sendDATACommand */ false);
|
||||
|
||||
// Send the message by chunks
|
||||
SMTPChunkingOutputStreamAdapter chunkStream(m_connection);
|
||||
|
||||
msg->generate(ctx, chunkStream);
|
||||
|
||||
chunkStream.flush();
|
||||
}
|
||||
|
||||
|
||||
|
@ -46,6 +46,7 @@ VMIME_TEST_SUITE_BEGIN(SMTPCommandTest)
|
||||
VMIME_TEST(testRCPT_UTF8)
|
||||
VMIME_TEST(testRSET)
|
||||
VMIME_TEST(testDATA)
|
||||
VMIME_TEST(testBDAT)
|
||||
VMIME_TEST(testNOOP)
|
||||
VMIME_TEST(testQUIT)
|
||||
VMIME_TEST(testWriteToSocket)
|
||||
@ -168,6 +169,19 @@ VMIME_TEST_SUITE_BEGIN(SMTPCommandTest)
|
||||
VASSERT_EQ("Text", "DATA", cmd->getText());
|
||||
}
|
||||
|
||||
void testBDAT()
|
||||
{
|
||||
vmime::ref <SMTPCommand> cmd1 = SMTPCommand::BDAT(12345, false);
|
||||
|
||||
VASSERT_NOT_NULL("Not null", cmd1);
|
||||
VASSERT_EQ("Text", "BDAT 12345", cmd1->getText());
|
||||
|
||||
vmime::ref <SMTPCommand> cmd2 = SMTPCommand::BDAT(67890, true);
|
||||
|
||||
VASSERT_NOT_NULL("Not null", cmd2);
|
||||
VASSERT_EQ("Text", "BDAT 67890 LAST", cmd2->getText());
|
||||
}
|
||||
|
||||
void testNOOP()
|
||||
{
|
||||
vmime::ref <SMTPCommand> cmd = SMTPCommand::NOOP();
|
||||
|
@ -23,9 +23,14 @@
|
||||
|
||||
#include "tests/testUtils.hpp"
|
||||
|
||||
#include "vmime/net/smtp/SMTPTransport.hpp"
|
||||
#include "vmime/net/smtp/SMTPChunkingOutputStreamAdapter.hpp"
|
||||
|
||||
|
||||
class greetingErrorSMTPTestSocket;
|
||||
class MAILandRCPTSMTPTestSocket;
|
||||
class chunkingSMTPTestSocket;
|
||||
class SMTPTestMessage;
|
||||
|
||||
|
||||
VMIME_TEST_SUITE_BEGIN(SMTPTransportTest)
|
||||
@ -33,6 +38,7 @@ VMIME_TEST_SUITE_BEGIN(SMTPTransportTest)
|
||||
VMIME_TEST_LIST_BEGIN
|
||||
VMIME_TEST(testGreetingError)
|
||||
VMIME_TEST(testMAILandRCPT)
|
||||
VMIME_TEST(testChunking)
|
||||
VMIME_TEST_LIST_END
|
||||
|
||||
|
||||
@ -77,6 +83,32 @@ VMIME_TEST_SUITE_BEGIN(SMTPTransportTest)
|
||||
tr->send(exp, recips, is, 0);
|
||||
}
|
||||
|
||||
void testChunking()
|
||||
{
|
||||
vmime::ref <vmime::net::session> session =
|
||||
vmime::create <vmime::net::session>();
|
||||
|
||||
vmime::ref <vmime::net::transport> tr = session->getTransport
|
||||
(vmime::utility::url("smtp://localhost"));
|
||||
|
||||
tr->setSocketFactory(vmime::create <testSocketFactory <chunkingSMTPTestSocket> >());
|
||||
tr->setTimeoutHandlerFactory(vmime::create <testTimeoutHandlerFactory>());
|
||||
|
||||
tr->connect();
|
||||
|
||||
VASSERT("Test server should report it supports the CHUNKING extension!",
|
||||
tr.dynamicCast <vmime::net::smtp::SMTPTransport>()->getConnection()->hasExtension("CHUNKING"));
|
||||
|
||||
vmime::mailbox exp("expeditor@test.vmime.org");
|
||||
|
||||
vmime::mailboxList recips;
|
||||
recips.appendMailbox(vmime::create <vmime::mailbox>("recipient@test.vmime.org"));
|
||||
|
||||
vmime::ref <vmime::message> msg = vmime::create <SMTPTestMessage>();
|
||||
|
||||
tr->send(msg, exp, recips);
|
||||
}
|
||||
|
||||
VMIME_TEST_SUITE_END
|
||||
|
||||
|
||||
@ -121,6 +153,13 @@ public:
|
||||
m_recipients.insert("recipient3@test.vmime.org");
|
||||
|
||||
m_state = STATE_NOT_CONNECTED;
|
||||
m_ehloSent = m_heloSent = m_mailSent = m_rcptSent = m_dataSent = m_quitSent = false;
|
||||
}
|
||||
|
||||
~MAILandRCPTSMTPTestSocket()
|
||||
{
|
||||
VASSERT("Client must send the DATA command", m_dataSent);
|
||||
VASSERT("Client must send the QUIT command", m_quitSent);
|
||||
}
|
||||
|
||||
void onConnected()
|
||||
@ -155,15 +194,29 @@ public:
|
||||
{
|
||||
localSend("500 Syntax error, command unrecognized\r\n");
|
||||
}
|
||||
else if (cmd == "EHLO")
|
||||
{
|
||||
localSend("502 Command not implemented\r\n");
|
||||
|
||||
m_ehloSent = true;
|
||||
}
|
||||
else if (cmd == "HELO")
|
||||
{
|
||||
VASSERT("Client must send the EHLO command before HELO", m_ehloSent);
|
||||
|
||||
localSend("250 OK\r\n");
|
||||
|
||||
m_heloSent = true;
|
||||
}
|
||||
else if (cmd == "MAIL")
|
||||
{
|
||||
VASSERT("Client must send the HELO command", m_heloSent);
|
||||
|
||||
VASSERT_EQ("MAIL", std::string("MAIL FROM:<expeditor@test.vmime.org>"), line);
|
||||
|
||||
localSend("250 OK\r\n");
|
||||
|
||||
m_mailSent = true;
|
||||
}
|
||||
else if (cmd == "RCPT")
|
||||
{
|
||||
@ -186,15 +239,21 @@ public:
|
||||
m_recipients.erase(it);
|
||||
|
||||
localSend("250 OK, recipient accepted\r\n");
|
||||
|
||||
m_rcptSent = true;
|
||||
}
|
||||
else if (cmd == "DATA")
|
||||
{
|
||||
VASSERT("Client must send the MAIL command", m_mailSent);
|
||||
VASSERT("Client must send the RCPT command", m_rcptSent);
|
||||
VASSERT("All recipients", m_recipients.empty());
|
||||
|
||||
localSend("354 Ready to accept data; end with <CRLF>.<CRLF>\r\n");
|
||||
|
||||
m_state = STATE_DATA;
|
||||
m_msgData.clear();
|
||||
|
||||
m_dataSent = true;
|
||||
}
|
||||
else if (cmd == "NOOP")
|
||||
{
|
||||
@ -202,6 +261,8 @@ public:
|
||||
}
|
||||
else if (cmd == "QUIT")
|
||||
{
|
||||
m_quitSent = true;
|
||||
|
||||
localSend("221 test.vmime.org Service closing transmission channel\r\n");
|
||||
}
|
||||
else
|
||||
@ -247,6 +308,225 @@ private:
|
||||
std::set <vmime::string> m_recipients;
|
||||
|
||||
std::string m_msgData;
|
||||
|
||||
bool m_ehloSent, m_heloSent, m_mailSent, m_rcptSent,
|
||||
m_dataSent, m_quitSent;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** SMTP test server 2.
|
||||
*
|
||||
* Test CHUNKING extension/BDAT command.
|
||||
*/
|
||||
class chunkingSMTPTestSocket : public testSocket
|
||||
{
|
||||
public:
|
||||
|
||||
chunkingSMTPTestSocket()
|
||||
{
|
||||
m_state = STATE_NOT_CONNECTED;
|
||||
m_bdatChunkCount = 0;
|
||||
m_ehloSent = m_mailSent = m_rcptSent = m_quitSent = false;
|
||||
}
|
||||
|
||||
~chunkingSMTPTestSocket()
|
||||
{
|
||||
VASSERT_EQ("BDAT chunk count", 3, m_bdatChunkCount);
|
||||
VASSERT("Client must send the QUIT command", m_quitSent);
|
||||
}
|
||||
|
||||
void onConnected()
|
||||
{
|
||||
localSend("220 test.vmime.org Service ready\r\n");
|
||||
processCommand();
|
||||
|
||||
m_state = STATE_COMMAND;
|
||||
}
|
||||
|
||||
void onDataReceived()
|
||||
{
|
||||
if (m_state == STATE_DATA)
|
||||
{
|
||||
if (m_bdatChunkReceived != m_bdatChunkSize)
|
||||
{
|
||||
const size_type remaining = m_bdatChunkSize - m_bdatChunkReceived;
|
||||
const size_type received = localReceiveRaw(NULL, remaining);
|
||||
|
||||
m_bdatChunkReceived += received;
|
||||
}
|
||||
|
||||
if (m_bdatChunkReceived == m_bdatChunkSize)
|
||||
{
|
||||
m_state = STATE_COMMAND;
|
||||
}
|
||||
}
|
||||
|
||||
processCommand();
|
||||
}
|
||||
|
||||
void processCommand()
|
||||
{
|
||||
vmime::string line;
|
||||
|
||||
if (!localReceiveLine(line))
|
||||
return;
|
||||
|
||||
std::istringstream iss(line);
|
||||
|
||||
switch (m_state)
|
||||
{
|
||||
case STATE_NOT_CONNECTED:
|
||||
|
||||
localSend("451 Requested action aborted: invalid state\r\n");
|
||||
break;
|
||||
|
||||
case STATE_COMMAND:
|
||||
{
|
||||
std::string cmd;
|
||||
iss >> cmd;
|
||||
|
||||
if (cmd == "EHLO")
|
||||
{
|
||||
localSend("250-test.vmime.org says hello\r\n");
|
||||
localSend("250 CHUNKING\r\n");
|
||||
|
||||
m_ehloSent = true;
|
||||
}
|
||||
else if (cmd == "HELO")
|
||||
{
|
||||
VASSERT("Client must not send the HELO command, as EHLO succeeded", false);
|
||||
}
|
||||
else if (cmd == "MAIL")
|
||||
{
|
||||
localSend("250 OK\r\n");
|
||||
|
||||
m_mailSent = true;
|
||||
}
|
||||
else if (cmd == "RCPT")
|
||||
{
|
||||
localSend("250 OK, recipient accepted\r\n");
|
||||
|
||||
m_rcptSent = true;
|
||||
}
|
||||
else if (cmd == "DATA")
|
||||
{
|
||||
VASSERT("BDAT must be used here!", false);
|
||||
}
|
||||
else if (cmd == "BDAT")
|
||||
{
|
||||
VASSERT("Client must send the MAIL command", m_mailSent);
|
||||
VASSERT("Client must send the RCPT command", m_rcptSent);
|
||||
|
||||
unsigned long chunkSize = 0;
|
||||
iss >> chunkSize;
|
||||
|
||||
std::string last;
|
||||
iss >> last;
|
||||
|
||||
if (m_bdatChunkCount == 0)
|
||||
{
|
||||
VASSERT_EQ("BDAT chunk1 size", 262144, chunkSize);
|
||||
VASSERT_EQ("BDAT chunk1 last", "", last);
|
||||
}
|
||||
else if (m_bdatChunkCount == 1)
|
||||
{
|
||||
VASSERT_EQ("BDAT chunk2 size", 262144, chunkSize);
|
||||
VASSERT_EQ("BDAT chunk2 last", "", last);
|
||||
}
|
||||
else if (m_bdatChunkCount == 2)
|
||||
{
|
||||
VASSERT_EQ("BDAT chunk3 size", 4712, chunkSize);
|
||||
VASSERT_EQ("BDAT chunk3 last", "LAST", last);
|
||||
}
|
||||
else
|
||||
{
|
||||
VASSERT("No more BDAT command should be issued!", false);
|
||||
}
|
||||
|
||||
m_bdatChunkSize = chunkSize;
|
||||
m_bdatChunkReceived = 0;
|
||||
m_bdatChunkCount++;
|
||||
m_state = STATE_DATA;
|
||||
|
||||
localSend("250 chunk received\r\n");
|
||||
}
|
||||
else if (cmd == "NOOP")
|
||||
{
|
||||
localSend("250 Completed\r\n");
|
||||
}
|
||||
else if (cmd == "QUIT")
|
||||
{
|
||||
localSend("221 test.vmime.org Service closing transmission channel\r\n");
|
||||
|
||||
m_quitSent = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
localSend("502 Command not implemented\r\n");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
processCommand();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
enum State
|
||||
{
|
||||
STATE_NOT_CONNECTED,
|
||||
STATE_COMMAND,
|
||||
STATE_DATA
|
||||
};
|
||||
|
||||
int m_state;
|
||||
int m_bdatChunkCount;
|
||||
int m_bdatChunkSize, m_bdatChunkReceived;
|
||||
|
||||
bool m_ehloSent, m_mailSent, m_rcptSent, m_quitSent;
|
||||
};
|
||||
|
||||
|
||||
class SMTPTestMessage : public vmime::message
|
||||
{
|
||||
public:
|
||||
|
||||
vmime::utility::stream::size_type getChunkBufferSize() const
|
||||
{
|
||||
static vmime::net::smtp::SMTPChunkingOutputStreamAdapter chunkStream(NULL);
|
||||
return chunkStream.getBlockSize();
|
||||
}
|
||||
|
||||
const std::vector <vmime::string>& getChunks() const
|
||||
{
|
||||
static std::vector <vmime::string> chunks;
|
||||
|
||||
if (chunks.size() == 0)
|
||||
{
|
||||
chunks.push_back(vmime::string(1000, 'A'));
|
||||
chunks.push_back(vmime::string(3000, 'B'));
|
||||
chunks.push_back(vmime::string(500000, 'C'));
|
||||
chunks.push_back(vmime::string(25000, 'D'));
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
void generateImpl(const vmime::generationContext& /* ctx */,
|
||||
vmime::utility::outputStream& outputStream,
|
||||
const vmime::string::size_type /* curLinePos */ = 0,
|
||||
vmime::string::size_type* /* newLinePos */ = NULL) const
|
||||
{
|
||||
for (unsigned int i = 0, n = getChunks().size() ; i < n ; ++i)
|
||||
{
|
||||
const vmime::string& chunk = getChunks()[i];
|
||||
outputStream.write(chunk.data(), chunk.size());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
@ -127,6 +127,42 @@ void testSocket::localReceive(vmime::string& buffer)
|
||||
}
|
||||
|
||||
|
||||
bool testSocket::localReceiveLine(vmime::string& line)
|
||||
{
|
||||
vmime::string::size_type eol;
|
||||
|
||||
if ((eol = m_outBuffer.find('\n')) != vmime::string::npos)
|
||||
{
|
||||
line = vmime::string(m_outBuffer.begin(), m_outBuffer.begin() + eol);
|
||||
|
||||
if (!line.empty() && line[line.length() - 1] == '\r')
|
||||
line.erase(line.end() - 1, line.end());
|
||||
|
||||
m_outBuffer.erase(m_outBuffer.begin(), m_outBuffer.begin() + eol + 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
testSocket::size_type testSocket::localReceiveRaw(char* buffer, const size_type count)
|
||||
{
|
||||
const size_type received = std::min(count, static_cast <size_type>(m_outBuffer.size()));
|
||||
|
||||
if (received != 0)
|
||||
{
|
||||
if (buffer != NULL)
|
||||
std::copy(m_outBuffer.begin(), m_outBuffer.begin() + received, buffer);
|
||||
|
||||
m_outBuffer.erase(m_outBuffer.begin(), m_outBuffer.begin() + received);
|
||||
}
|
||||
|
||||
return received;
|
||||
}
|
||||
|
||||
|
||||
void testSocket::onDataReceived()
|
||||
{
|
||||
// Override
|
||||
|
@ -256,16 +256,31 @@ public:
|
||||
|
||||
/** Send data to client.
|
||||
*
|
||||
* @buffer data to send
|
||||
* @param buffer data to send
|
||||
*/
|
||||
void localSend(const vmime::string& buffer);
|
||||
|
||||
/** Receive data from client.
|
||||
*
|
||||
* @buffer buffer in which to store received data
|
||||
* @param buffer buffer in which to store received data
|
||||
*/
|
||||
void localReceive(vmime::string& buffer);
|
||||
|
||||
/** Receive a line from client.
|
||||
*
|
||||
* @param buffer buffer in which to store received line
|
||||
* @return true if a line has been read, or false otherwise
|
||||
*/
|
||||
bool localReceiveLine(vmime::string& buffer);
|
||||
|
||||
/** Receive data from client.
|
||||
*
|
||||
* @param buffer buffer in which to store received data
|
||||
* @param count number of bytes to receive
|
||||
* @return number of bytes received
|
||||
*/
|
||||
testSocket::size_type localReceiveRaw(char* buffer, const size_type count);
|
||||
|
||||
protected:
|
||||
|
||||
/** Called when the client has sent some data.
|
||||
|
84
vmime/net/smtp/SMTPChunkingOutputStreamAdapter.hpp
Normal file
84
vmime/net/smtp/SMTPChunkingOutputStreamAdapter.hpp
Normal file
@ -0,0 +1,84 @@
|
||||
//
|
||||
// VMime library (http://www.vmime.org)
|
||||
// Copyright (C) 2002-2013 Vincent Richard <vincent@vmime.org>
|
||||
//
|
||||
// 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_SMTPCHUNKINGOUTPUTSTREAMADAPTER_HPP_INCLUDED
|
||||
#define VMIME_NET_SMTP_SMTPCHUNKINGOUTPUTSTREAMADAPTER_HPP_INCLUDED
|
||||
|
||||
|
||||
#include "vmime/config.hpp"
|
||||
|
||||
|
||||
#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP
|
||||
|
||||
|
||||
#include "vmime/utility/outputStream.hpp"
|
||||
|
||||
|
||||
namespace vmime {
|
||||
namespace net {
|
||||
namespace smtp {
|
||||
|
||||
|
||||
class SMTPConnection;
|
||||
|
||||
|
||||
/** An output stream adapter used with ESMTP CHUNKING extension.
|
||||
*/
|
||||
class VMIME_EXPORT SMTPChunkingOutputStreamAdapter : public utility::outputStream
|
||||
{
|
||||
friend class vmime::creator;
|
||||
|
||||
public:
|
||||
|
||||
SMTPChunkingOutputStreamAdapter(ref <SMTPConnection> conn);
|
||||
|
||||
void write(const value_type* const data, const size_type count);
|
||||
void flush();
|
||||
|
||||
size_type getBlockSize();
|
||||
|
||||
private:
|
||||
|
||||
SMTPChunkingOutputStreamAdapter(const SMTPChunkingOutputStreamAdapter&);
|
||||
|
||||
|
||||
void sendChunk(const value_type* const data, const size_type count, const bool last);
|
||||
|
||||
|
||||
ref <SMTPConnection> m_connection;
|
||||
|
||||
value_type m_buffer[262144]; // 256 KB
|
||||
size_type m_bufferSize;
|
||||
|
||||
unsigned int m_chunkCount;
|
||||
};
|
||||
|
||||
|
||||
} // smtp
|
||||
} // net
|
||||
} // vmime
|
||||
|
||||
|
||||
#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP
|
||||
|
||||
#endif // VMIME_NET_SMTP_SMTPCHUNKINGOUTPUTSTREAMADAPTER_HPP_INCLUDED
|
@ -67,6 +67,7 @@ public:
|
||||
static ref <SMTPCommand> RCPT(const mailbox& mbox, const bool utf8);
|
||||
static ref <SMTPCommand> RSET();
|
||||
static ref <SMTPCommand> DATA();
|
||||
static ref <SMTPCommand> BDAT(const unsigned long chunkSize, const bool last);
|
||||
static ref <SMTPCommand> NOOP();
|
||||
static ref <SMTPCommand> QUIT();
|
||||
|
||||
|
@ -91,6 +91,22 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
/** Send the MAIL and RCPT commands to the server, checking the
|
||||
* response, and using pipelining if supported by the server.
|
||||
* Optionally, the DATA command can also be sent.
|
||||
*
|
||||
* @param expeditor expeditor mailbox
|
||||
* @param recipients list of recipient mailboxes
|
||||
* @param sender envelope sender (if empty, expeditor will be used)
|
||||
* @param sendDATACommand if true, the DATA command will be sent
|
||||
*/
|
||||
void sendEnvelope
|
||||
(const mailbox& expeditor,
|
||||
const mailboxList& recipients,
|
||||
const mailbox& sender,
|
||||
bool sendDATACommand);
|
||||
|
||||
|
||||
ref <SMTPConnection> m_connection;
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user