diff options
-rw-r--r-- | src/vmime/net/smtp/SMTPCommand.cpp | 3 | ||||
-rw-r--r-- | src/vmime/net/smtp/SMTPTransport.cpp | 34 | ||||
-rw-r--r-- | src/vmime/net/smtp/SMTPTransport.hpp | 2 | ||||
-rw-r--r-- | tests/net/smtp/SMTPTransportTest.cpp | 72 | ||||
-rw-r--r-- | tests/net/smtp/SMTPTransportTestUtils.hpp | 182 | ||||
-rw-r--r-- | tests/testUtils.cpp | 14 |
6 files changed, 304 insertions, 3 deletions
diff --git a/src/vmime/net/smtp/SMTPCommand.cpp b/src/vmime/net/smtp/SMTPCommand.cpp index 27c8ec1b..04c78722 100644 --- a/src/vmime/net/smtp/SMTPCommand.cpp +++ b/src/vmime/net/smtp/SMTPCommand.cpp @@ -153,6 +153,9 @@ shared_ptr <SMTPCommand> SMTPCommand::RCPT(const mailbox& mbox, const bool utf8) cmd << ">"; + if (utf8) + cmd << " SMTPUTF8"; + return createCommand(cmd.str()); } diff --git a/src/vmime/net/smtp/SMTPTransport.cpp b/src/vmime/net/smtp/SMTPTransport.cpp index 2af6a195..fb9ea9aa 100644 --- a/src/vmime/net/smtp/SMTPTransport.cpp +++ b/src/vmime/net/smtp/SMTPTransport.cpp @@ -152,6 +152,22 @@ void SMTPTransport::noop() } +// static +bool SMTPTransport::mailboxNeedsUTF8(const mailbox& mb) +{ + bool all7bit = + utility::stringUtils::is7bit(mb.getEmail().getLocalName().getBuffer()) + && utility::stringUtils::is7bit(mb.getEmail().getDomainName().getBuffer()); + + for (size_t i = 0, n = mb.getName().getWordCount() ; all7bit && i != n ; ++i) + { + all7bit = utility::stringUtils::is7bit(mb.getName().getWordAt(i)->getBuffer()); + } + + return !all7bit; +} + + void SMTPTransport::sendEnvelope (const mailbox& expeditor, const mailboxList& recipients, const mailbox& sender, bool sendDATACommand, @@ -181,9 +197,21 @@ void SMTPTransport::sendEnvelope const bool hasSize = m_connection->hasExtension("SIZE"); if (!sender.isEmpty()) - commands->addCommand(SMTPCommand::MAIL(sender, hasSMTPUTF8, hasSize ? size : 0)); + { + commands->addCommand( + SMTPCommand::MAIL( + sender, hasSMTPUTF8 && mailboxNeedsUTF8(sender), hasSize ? size : 0 + ) + ); + } else - commands->addCommand(SMTPCommand::MAIL(expeditor, hasSMTPUTF8, hasSize ? size : 0)); + { + commands->addCommand( + SMTPCommand::MAIL( + expeditor, hasSMTPUTF8 && mailboxNeedsUTF8(expeditor), hasSize ? size : 0 + ) + ); + } // Now, we will need to reset next time m_needReset = true; @@ -192,7 +220,7 @@ void SMTPTransport::sendEnvelope for (size_t i = 0 ; i < recipients.getMailboxCount() ; ++i) { const mailbox& mbox = *recipients.getMailboxAt(i); - commands->addCommand(SMTPCommand::RCPT(mbox, hasSMTPUTF8)); + commands->addCommand(SMTPCommand::RCPT(mbox, hasSMTPUTF8 && mailboxNeedsUTF8(mbox))); } // Prepare sending of message data diff --git a/src/vmime/net/smtp/SMTPTransport.hpp b/src/vmime/net/smtp/SMTPTransport.hpp index a0f02418..7b266d3d 100644 --- a/src/vmime/net/smtp/SMTPTransport.hpp +++ b/src/vmime/net/smtp/SMTPTransport.hpp @@ -91,6 +91,8 @@ public: private: + static bool mailboxNeedsUTF8(const mailbox& mb); + /** 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. diff --git a/tests/net/smtp/SMTPTransportTest.cpp b/tests/net/smtp/SMTPTransportTest.cpp index dd5546cc..d1a31a5a 100644 --- a/tests/net/smtp/SMTPTransportTest.cpp +++ b/tests/net/smtp/SMTPTransportTest.cpp @@ -39,6 +39,8 @@ VMIME_TEST_SUITE_BEGIN(SMTPTransportTest) VMIME_TEST(testChunking) VMIME_TEST(testSize_Chunking) VMIME_TEST(testSize_NoChunking) + VMIME_TEST(testSMTPUTF8_available) + VMIME_TEST(testSMTPUTF8_notAvailable) VMIME_TEST_LIST_END @@ -168,5 +170,75 @@ VMIME_TEST_SUITE_BEGIN(SMTPTransportTest) vmime::net::smtp::SMTPMessageSizeExceedsMaxLimitsException); } + void testSMTPUTF8_available() + { + vmime::shared_ptr <vmime::net::session> session = vmime::net::session::create(); + + vmime::shared_ptr <vmime::net::transport> tr = session->getTransport + (vmime::utility::url("smtp://localhost")); + + tr->setSocketFactory(vmime::make_shared <testSocketFactory <UTF8SMTPTestSocket <true> > >()); + tr->setTimeoutHandlerFactory(vmime::make_shared <testTimeoutHandlerFactory>()); + + VASSERT_NO_THROW("Connection", tr->connect()); + + vmime::mailbox exp( + vmime::emailAddress( + vmime::word("expéditeur", vmime::charsets::UTF_8), + vmime::word("test.vmime.org") + ) + ); + + vmime::mailboxList recips; + recips.appendMailbox(vmime::make_shared <vmime::mailbox>("[email protected]")); + recips.appendMailbox(vmime::make_shared <vmime::mailbox>("[email protected]")); + recips.appendMailbox(vmime::make_shared <vmime::mailbox>( + vmime::emailAddress( + vmime::word("récepteur", vmime::charsets::UTF_8), + vmime::word("test.vmime.org") + ) + )); + + vmime::string data("Message data"); + vmime::utility::inputStreamStringAdapter is(data); + + tr->send(exp, recips, is, 0); + } + + void testSMTPUTF8_notAvailable() + { + vmime::shared_ptr <vmime::net::session> session = vmime::net::session::create(); + + vmime::shared_ptr <vmime::net::transport> tr = session->getTransport + (vmime::utility::url("smtp://localhost")); + + tr->setSocketFactory(vmime::make_shared <testSocketFactory <UTF8SMTPTestSocket <false> > >()); + tr->setTimeoutHandlerFactory(vmime::make_shared <testTimeoutHandlerFactory>()); + + VASSERT_NO_THROW("Connection", tr->connect()); + + vmime::mailbox exp( + vmime::emailAddress( + vmime::word("expéditeur", vmime::charsets::UTF_8), + vmime::word("test.vmime.org") + ) + ); + + vmime::mailboxList recips; + recips.appendMailbox(vmime::make_shared <vmime::mailbox>("[email protected]")); + recips.appendMailbox(vmime::make_shared <vmime::mailbox>("[email protected]")); + recips.appendMailbox(vmime::make_shared <vmime::mailbox>( + vmime::emailAddress( + vmime::word("récepteur", vmime::charsets::UTF_8), + vmime::word("test.vmime.org") + ) + )); + + vmime::string data("Message data"); + vmime::utility::inputStreamStringAdapter is(data); + + tr->send(exp, recips, is, 0); + } + VMIME_TEST_SUITE_END diff --git a/tests/net/smtp/SMTPTransportTestUtils.hpp b/tests/net/smtp/SMTPTransportTestUtils.hpp index c1224216..cfd830cf 100644 --- a/tests/net/smtp/SMTPTransportTestUtils.hpp +++ b/tests/net/smtp/SMTPTransportTestUtils.hpp @@ -591,3 +591,185 @@ public: }; typedef SMTPBigTestMessage <4194304> SMTPBigTestMessage4MB; + + + +/** SMTP test server for SMTPUTF8 extension. + */ +template <bool SUPPORTS_UTF8> +class UTF8SMTPTestSocket : public lineBasedTestSocket +{ +public: + + UTF8SMTPTestSocket() + { + if (SUPPORTS_UTF8) + { + m_rcptLines.insert("RCPT TO:<[email protected]>"); + m_rcptLines.insert("RCPT TO:<[email protected]>"); + m_rcptLines.insert("RCPT TO:<ré[email protected]> SMTPUTF8"); + } + else + { + m_rcptLines.insert("RCPT TO:<[email protected]>"); + m_rcptLines.insert("RCPT TO:<[email protected]>"); + m_rcptLines.insert("RCPT TO:<[email protected]>"); + } + + m_state = STATE_NOT_CONNECTED; + m_ehloSent = m_mailSent = m_rcptSent = m_dataSent = m_quitSent = false; + } + + ~UTF8SMTPTestSocket() + { + } + + void onConnected() + { + localSend("220 test.vmime.org Service ready\r\n"); + processCommand(); + + m_state = STATE_COMMAND; + } + + void processCommand() + { + if (!haveMoreLines()) + return; + + vmime::string line = getNextLine(); + 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.empty()) + { + localSend("500 Syntax error, command unrecognized\r\n"); + } + else if (cmd == "EHLO") + { + if (SUPPORTS_UTF8) + { + localSend("250-test.vmime.org\r\n"); + localSend("250 SMTPUTF8\r\n"); + } + else + { + localSend("250 test.vmime.org\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") + { + VASSERT("Client must send the EHLO command", m_ehloSent); + VASSERT("The MAIL command must be sent only one time", !m_mailSent); + + if (SUPPORTS_UTF8) + { + VASSERT_EQ("MAIL", std::string("MAIL FROM:<expé[email protected]> SMTPUTF8"), line); + } + else + { + VASSERT_EQ("MAIL", std::string("MAIL FROM:<[email protected]>"), line); + } + + localSend("250 OK\r\n"); + + m_mailSent = true; + } + else if (cmd == "RCPT") + { + std::set <vmime::string>::iterator it = m_rcptLines.find(line); + + VASSERT(std::string("RCPT not found: '") + line + "'", it != m_rcptLines.end()); + + m_rcptLines.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_rcptLines.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") + { + localSend("250 Completed\r\n"); + } + else if (cmd == "QUIT") + { + m_quitSent = true; + + localSend("221 test.vmime.org Service closing transmission channel\r\n"); + } + else + { + localSend("502 Command not implemented\r\n"); + } + + break; + } + case STATE_DATA: + { + if (line == ".") + { + VASSERT_EQ("Data", "Message data\r\n", m_msgData); + + localSend("250 Message accepted for delivery\r\n"); + m_state = STATE_COMMAND; + } + else + { + m_msgData += line + "\r\n"; + } + + break; + } + + } + + processCommand(); + } + +private: + + enum State + { + STATE_NOT_CONNECTED, + STATE_COMMAND, + STATE_DATA + }; + + int m_state; + + std::set <vmime::string> m_rcptLines; + + std::string m_msgData; + + bool m_ehloSent, m_mailSent, m_rcptSent, m_dataSent, m_quitSent; +}; diff --git a/tests/testUtils.cpp b/tests/testUtils.cpp index 0eb2bfd4..1e8e0c99 100644 --- a/tests/testUtils.cpp +++ b/tests/testUtils.cpp @@ -26,6 +26,11 @@ #include "vmime/utility/stringUtils.hpp" #include <cstring> +#include <iostream> + + +// Enable to output socket send/receive on standard output +#define DEBUG_SOCKET_IN_OUT 0 @@ -156,6 +161,11 @@ vmime::size_t testSocket::sendRawNonBlocking(const vmime::byte_t* buffer, const void testSocket::localSend(const vmime::string& buffer) { m_inBuffer += buffer; + +#if DEBUG_SOCKET_IN_OUT + std::cout << "> " << vmime::utility::stringUtils::trim(buffer) << std::endl; +#endif // DEBUG_SOCKET_IN_OUT + } @@ -232,6 +242,10 @@ void lineBasedTestSocket::onDataReceived() if (!line.empty() && line[line.length() - 1] == '\r') line.erase(line.end() - 1, line.end()); +#if DEBUG_SOCKET_IN_OUT + std::cout << "< " << vmime::utility::stringUtils::trim(line) << std::endl; +#endif // DEBUG_SOCKET_IN_OUT + m_lines.push_back(line); m_buffer.erase(m_buffer.begin(), m_buffer.begin() + eol + 1); } |