aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorVincent Richard <[email protected]>2013-06-20 09:02:39 +0000
committerVincent Richard <[email protected]>2013-06-20 09:02:39 +0000
commiteee80fc81e007baacbce80afe55e69a52bcfd1f6 (patch)
tree4ed1aeecac70ef7ab614f2956e306bbc427fe837 /tests
parentMoved SMTP connection-related things to SMTPConnection object. (diff)
downloadvmime-eee80fc81e007baacbce80afe55e69a52bcfd1f6.tar.gz
vmime-eee80fc81e007baacbce80afe55e69a52bcfd1f6.zip
Added support for CHUNKING SMTP extension (RFC-3030). More robust unit tests for SMTP.
Diffstat (limited to 'tests')
-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
4 files changed, 347 insertions, 2 deletions
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.