Fixed #186: use SMTPUTF8 only when needed.

This commit is contained in:
Vincent Richard 2017-12-14 21:39:29 +01:00
parent eb5d9db693
commit 1592cccb61
6 changed files with 304 additions and 3 deletions

View File

@ -153,6 +153,9 @@ shared_ptr <SMTPCommand> SMTPCommand::RCPT(const mailbox& mbox, const bool utf8)
cmd << ">";
if (utf8)
cmd << " SMTPUTF8";
return createCommand(cmd.str());
}

View File

@ -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

View File

@ -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.

View File

@ -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>("recipient1@test.vmime.org"));
recips.appendMailbox(vmime::make_shared <vmime::mailbox>("recipient2@test.vmime.org"));
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>("recipient1@test.vmime.org"));
recips.appendMailbox(vmime::make_shared <vmime::mailbox>("recipient2@test.vmime.org"));
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

View File

@ -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:<recipient1@test.vmime.org>");
m_rcptLines.insert("RCPT TO:<recipient2@test.vmime.org>");
m_rcptLines.insert("RCPT TO:<récepteur@test.vmime.org> SMTPUTF8");
}
else
{
m_rcptLines.insert("RCPT TO:<recipient1@test.vmime.org>");
m_rcptLines.insert("RCPT TO:<recipient2@test.vmime.org>");
m_rcptLines.insert("RCPT TO:<=?utf-8?Q?r=C3=A9cepteur?=@test.vmime.org>");
}
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éditeur@test.vmime.org> SMTPUTF8"), line);
}
else
{
VASSERT_EQ("MAIL", std::string("MAIL FROM:<=?utf-8?Q?exp=C3=A9diteur?=@test.vmime.org>"), 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;
};

View File

@ -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);
}