From eee80fc81e007baacbce80afe55e69a52bcfd1f6 Mon Sep 17 00:00:00 2001 From: Vincent Richard Date: Thu, 20 Jun 2013 11:02:39 +0200 Subject: [PATCH] Added support for CHUNKING SMTP extension (RFC-3030). More robust unit tests for SMTP. --- SConstruct | 1 + .../smtp/SMTPChunkingOutputStreamAdapter.cpp | 145 +++++++++ src/net/smtp/SMTPCommand.cpp | 14 + src/net/smtp/SMTPTransport.cpp | 72 +++-- tests/net/smtp/SMTPCommandTest.cpp | 14 + tests/net/smtp/SMTPTransportTest.cpp | 280 ++++++++++++++++++ tests/testUtils.cpp | 36 +++ tests/testUtils.hpp | 19 +- .../smtp/SMTPChunkingOutputStreamAdapter.hpp | 84 ++++++ vmime/net/smtp/SMTPCommand.hpp | 1 + vmime/net/smtp/SMTPTransport.hpp | 16 + 11 files changed, 661 insertions(+), 21 deletions(-) create mode 100644 src/net/smtp/SMTPChunkingOutputStreamAdapter.cpp create mode 100644 vmime/net/smtp/SMTPChunkingOutputStreamAdapter.hpp 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 +// +// 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 + + +namespace vmime { +namespace net { +namespace smtp { + + +SMTPChunkingOutputStreamAdapter::SMTPChunkingOutputStreamAdapter(ref 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 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 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 @@ -149,6 +149,20 @@ ref SMTPCommand::DATA() } +// static +ref 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::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 resp; + if ((resp = m_connection->readResponse())->getCode() != 250) { disconnect(); @@ -270,21 +286,39 @@ void SMTPTransport::send (ref 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(); } 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 cmd1 = SMTPCommand::BDAT(12345, false); + + VASSERT_NOT_NULL("Not null", cmd1); + VASSERT_EQ("Text", "BDAT 12345", cmd1->getText()); + + vmime::ref cmd2 = SMTPCommand::BDAT(67890, true); + + VASSERT_NOT_NULL("Not null", cmd2); + VASSERT_EQ("Text", "BDAT 67890 LAST", cmd2->getText()); + } + void testNOOP() { vmime::ref 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 session = + vmime::create (); + + vmime::ref tr = session->getTransport + (vmime::utility::url("smtp://localhost")); + + tr->setSocketFactory(vmime::create >()); + tr->setTimeoutHandlerFactory(vmime::create ()); + + tr->connect(); + + VASSERT("Test server should report it supports the CHUNKING extension!", + tr.dynamicCast ()->getConnection()->hasExtension("CHUNKING")); + + vmime::mailbox exp("expeditor@test.vmime.org"); + + vmime::mailboxList recips; + recips.appendMailbox(vmime::create ("recipient@test.vmime.org")); + + vmime::ref msg = vmime::create (); + + 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:"), 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 .\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 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 & getChunks() const + { + static std::vector 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 (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 +// +// 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 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 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 RCPT(const mailbox& mbox, const bool utf8); static ref RSET(); static ref DATA(); + static ref BDAT(const unsigned long chunkSize, const bool last); static ref NOOP(); static ref 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 m_connection;