diff options
author | Vincent Richard <[email protected]> | 2013-06-20 09:02:39 +0000 |
---|---|---|
committer | Vincent Richard <[email protected]> | 2013-06-20 09:02:39 +0000 |
commit | eee80fc81e007baacbce80afe55e69a52bcfd1f6 (patch) | |
tree | 4ed1aeecac70ef7ab614f2956e306bbc427fe837 /tests | |
parent | Moved SMTP connection-related things to SMTPConnection object. (diff) | |
download | vmime-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.cpp | 14 | ||||
-rw-r--r-- | tests/net/smtp/SMTPTransportTest.cpp | 280 | ||||
-rw-r--r-- | tests/testUtils.cpp | 36 | ||||
-rw-r--r-- | tests/testUtils.hpp | 19 |
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. |