aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--SConstruct1
-rw-r--r--src/net/smtp/SMTPChunkingOutputStreamAdapter.cpp145
-rw-r--r--src/net/smtp/SMTPCommand.cpp14
-rw-r--r--src/net/smtp/SMTPTransport.cpp72
-rw-r--r--tests/net/smtp/SMTPCommandTest.cpp14
-rw-r--r--tests/net/smtp/SMTPTransportTest.cpp280
-rw-r--r--tests/testUtils.cpp36
-rw-r--r--tests/testUtils.hpp19
-rw-r--r--vmime/net/smtp/SMTPChunkingOutputStreamAdapter.hpp84
-rw-r--r--vmime/net/smtp/SMTPCommand.hpp1
-rw-r--r--vmime/net/smtp/SMTPTransport.hpp16
11 files changed, 661 insertions, 21 deletions
diff --git a/SConstruct b/SConstruct
index aa477018..c810267f 100644
--- a/SConstruct
+++ b/SConstruct
@@ -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',
diff --git a/src/net/smtp/SMTPChunkingOutputStreamAdapter.cpp b/src/net/smtp/SMTPChunkingOutputStreamAdapter.cpp
new file mode 100644
index 00000000..2561bf4a
--- /dev/null
+++ b/src/net/smtp/SMTPChunkingOutputStreamAdapter.cpp
@@ -0,0 +1,145 @@
+//
+// VMime library (http://www.vmime.org)
+// Copyright (C) 2002-2013 Vincent Richard <[email protected]>
+//
+// 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
diff --git a/src/net/smtp/SMTPCommand.cpp b/src/net/smtp/SMTPCommand.cpp
index d9f5c286..9813c4f5 100644
--- a/src/net/smtp/SMTPCommand.cpp
+++ b/src/net/smtp/SMTPCommand.cpp
@@ -150,6 +150,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()
{
return createCommand("NOOP");
diff --git a/src/net/smtp/SMTPTransport.cpp b/src/net/smtp/SMTPTransport.cpp
index 9e00ac4d..fb951bad 100644
--- a/src/net/smtp/SMTPTransport.cpp
+++ b/src/net/smtp/SMTPTransport.cpp
@@ -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);
+
+ msg->generate(ctx, ossAdapter);
+
+ const string& str(oss.str());
+
+ utility::inputStreamStringAdapter isAdapter(str);
+
+ send(expeditor, recipients, isAdapter, str.length(), progress, sender);
+ }
+
+ // Send message envelope
+ sendEnvelope(expeditor, recipients, sender, /* sendDATACommand */ false);
- const string& str(oss.str());
+ // Send the message by chunks
+ SMTPChunkingOutputStreamAdapter chunkStream(m_connection);
- utility::inputStreamStringAdapter isAdapter(str);
+ msg->generate(ctx, chunkStream);
- send(expeditor, recipients, isAdapter, str.length(), progress, sender);
+ chunkStream.flush();
}
diff --git a/tests/net/smtp/SMTPCommandTest.cpp b/tests/net/smtp/SMTPCommandTest.cpp
index d93bc729..ce9e7ce5 100644
--- a/tests/net/smtp/SMTPCommandTest.cpp
+++ b/tests/net/smtp/SMTPCommandTest.cpp
@@ -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();
diff --git a/tests/net/smtp/SMTPTransportTest.cpp b/tests/net/smtp/SMTPTransportTest.cpp
index 4ffda792..e1c32a9a 100644
--- a/tests/net/smtp/SMTPTransportTest.cpp
+++ b/tests/net/smtp/SMTPTransportTest.cpp
@@ -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("[email protected]");
+
+ vmime::mailboxList recips;
+ recips.appendMailbox(vmime::create <vmime::mailbox>("[email protected]"));
+
+ 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("[email protected]");
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:<[email protected]>"), 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());
+ }
+ }
};
diff --git a/tests/testUtils.cpp b/tests/testUtils.cpp
index cb6df133..93cb9e38 100644
--- a/tests/testUtils.cpp
+++ b/tests/testUtils.cpp
@@ -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
diff --git a/tests/testUtils.hpp b/tests/testUtils.hpp
index 1e138301..7ff3b40f 100644
--- a/tests/testUtils.hpp
+++ b/tests/testUtils.hpp
@@ -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.
diff --git a/vmime/net/smtp/SMTPChunkingOutputStreamAdapter.hpp b/vmime/net/smtp/SMTPChunkingOutputStreamAdapter.hpp
new file mode 100644
index 00000000..a29fbfb4
--- /dev/null
+++ b/vmime/net/smtp/SMTPChunkingOutputStreamAdapter.hpp
@@ -0,0 +1,84 @@
+//
+// VMime library (http://www.vmime.org)
+// Copyright (C) 2002-2013 Vincent Richard <[email protected]>
+//
+// 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
diff --git a/vmime/net/smtp/SMTPCommand.hpp b/vmime/net/smtp/SMTPCommand.hpp
index 48a5ce00..dcca5f2a 100644
--- a/vmime/net/smtp/SMTPCommand.hpp
+++ b/vmime/net/smtp/SMTPCommand.hpp
@@ -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();
diff --git a/vmime/net/smtp/SMTPTransport.hpp b/vmime/net/smtp/SMTPTransport.hpp
index 57ea43c3..3629d5d7 100644
--- a/vmime/net/smtp/SMTPTransport.hpp
+++ b/vmime/net/smtp/SMTPTransport.hpp
@@ -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;