aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/book/net.tex12
-rw-r--r--src/vmime/encoding.hpp2
-rw-r--r--src/vmime/mailbox.cpp6
-rw-r--r--src/vmime/net/smtp/SMTPConnection.cpp38
-rw-r--r--src/vmime/net/smtp/SMTPTransport.cpp50
-rw-r--r--src/vmime/net/smtp/SMTPTransport.hpp2
-rw-r--r--tests/net/smtp/SMTPTransportTest.cpp140
-rw-r--r--tests/net/smtp/SMTPTransportTestUtils.hpp190
-rw-r--r--tests/parser/mailboxTest.cpp10
-rw-r--r--tests/testUtils.cpp14
10 files changed, 440 insertions, 24 deletions
diff --git a/doc/book/net.tex b/doc/book/net.tex
index 1e5f1803..ac3b70c3 100644
--- a/doc/book/net.tex
+++ b/doc/book/net.tex
@@ -931,17 +931,7 @@ vmime::shared_ptr <vmime::security::cert::X509Certificate>
vmime::utility::inputStreamAdapter is(certFile);
vmime::shared_ptr <vmime::security::cert::X509Certificate> cert;
- // Try DER format
- cert = vmime::security::cert::X509Certificate::import
- (is, vmime::security::cert::X509Certificate::FORMAT_DER);
-
- if (cert != NULL)
- return cert;
-
- // Try PEM format
- is.reset();
- cert = vmime::security::cert::X509Certificate::import
- (is, vmime::security::cert::X509Certificate::FORMAT_PEM);
+ cert = vmime::security::cert::X509Certificate::import(is);
return cert;
}
diff --git a/src/vmime/encoding.hpp b/src/vmime/encoding.hpp
index 3148f899..90733807 100644
--- a/src/vmime/encoding.hpp
+++ b/src/vmime/encoding.hpp
@@ -58,8 +58,6 @@ public:
encoding(const string& name, const EncodingUsage usage);
encoding(const encoding& enc);
-public:
-
/** Return the name of the encoding.
* See the constants in vmime::encodingTypes.
*
diff --git a/src/vmime/mailbox.cpp b/src/vmime/mailbox.cpp
index 7c6d5e3b..c4b230c4 100644
--- a/src/vmime/mailbox.cpp
+++ b/src/vmime/mailbox.cpp
@@ -234,8 +234,6 @@ void mailbox::parseImpl
++p;
}
-
- break;
}
else
{
@@ -245,10 +243,6 @@ void mailbox::parseImpl
}
else if (state == State_Address)
{
- // Skip '<' character
- if (*p == '<')
- ++p;
-
bool escaped = false;
int comment = 0;
diff --git a/src/vmime/net/smtp/SMTPConnection.cpp b/src/vmime/net/smtp/SMTPConnection.cpp
index 3785126a..8709efe4 100644
--- a/src/vmime/net/smtp/SMTPConnection.cpp
+++ b/src/vmime/net/smtp/SMTPConnection.cpp
@@ -40,6 +40,10 @@
#if VMIME_HAVE_SASL_SUPPORT
#include "vmime/security/sasl/SASLContext.hpp"
+#else
+ #include "vmime/utility/encoder/b64Encoder.hpp"
+ #include "vmime/utility/inputStreamStringAdapter.hpp"
+ #include "vmime/utility/outputStreamStringAdapter.hpp"
#endif // VMIME_HAVE_SASL_SUPPORT
#if VMIME_HAVE_TLS_SUPPORT
@@ -48,7 +52,6 @@
#endif // VMIME_HAVE_TLS_SUPPORT
-
// Helpers for service properties
#define GET_PROPERTY(type, prop) \
(m_transport.lock()->getInfos().getPropertyValue <type>(getSession(), \
@@ -307,6 +310,39 @@ void SMTPConnection::authenticate()
throw;
}
}
+#else // no SASL
+
+ // allow AUTH PLAIN over TLS - it is a popular and simple mechanism
+ if (m_secured)
+ {
+ std::vector <string> authMechs;
+ hasExtension("AUTH", &authMechs);
+
+ if (authMechs.empty())
+ throw exceptions::authentication_error("No AUTH mechanism available.");
+
+ const string plain("PLAIN");
+ if (std::find(authMechs.begin(), authMechs.end(), plain) != authMechs.end())
+ {
+ const string username = getAuthenticator()->getUsername();
+ const string password = getAuthenticator()->getPassword();
+ const string authToken = username + '\0' + username + '\0' + password;
+ auto encoder = new vmime::utility::encoder::b64Encoder();
+ utility::inputStreamStringAdapter in(authToken);
+ string authTokenBase64;
+ utility::outputStreamStringAdapter out(authTokenBase64);
+ encoder->encode(in, out);
+ sendRequest(SMTPCommand::AUTH(plain, authTokenBase64));
+ shared_ptr <SMTPResponse> response = readResponse();
+ const int code = response ? response->getCode() : -1;
+ if (code == 235)
+ {
+ m_authenticated = true;
+ return;
+ }
+ }
+ }
+
#endif // VMIME_HAVE_SASL_SUPPORT
// No other authentication method is possible
diff --git a/src/vmime/net/smtp/SMTPTransport.cpp b/src/vmime/net/smtp/SMTPTransport.cpp
index 2af6a195..25eb72a7 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,
@@ -176,14 +192,40 @@ void SMTPTransport::sendEnvelope
if (needReset)
commands->addCommand(SMTPCommand::RSET());
- // Emit the "MAIL" command
+ // Check whether we need SMTPUTF8
const bool hasSMTPUTF8 = m_connection->hasExtension("SMTPUTF8");
+ bool needSMTPUTF8 = false;
+
+ if (!sender.isEmpty())
+ needSMTPUTF8 = needSMTPUTF8 || mailboxNeedsUTF8(sender);
+ else
+ needSMTPUTF8 = needSMTPUTF8 || mailboxNeedsUTF8(expeditor);
+
+ for (size_t i = 0 ; i < recipients.getMailboxCount() ; ++i)
+ {
+ const mailbox& mbox = *recipients.getMailboxAt(i);
+ needSMTPUTF8 = needSMTPUTF8 || mailboxNeedsUTF8(mbox);
+ }
+
+ // Emit the "MAIL" command
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 && needSMTPUTF8, hasSize ? size : 0
+ )
+ );
+ }
else
- commands->addCommand(SMTPCommand::MAIL(expeditor, hasSMTPUTF8, hasSize ? size : 0));
+ {
+ commands->addCommand(
+ SMTPCommand::MAIL(
+ expeditor, hasSMTPUTF8 && needSMTPUTF8, hasSize ? size : 0
+ )
+ );
+ }
// Now, we will need to reset next time
m_needReset = true;
@@ -192,7 +234,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 && needSMTPUTF8));
}
// 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..cbfab4f5 100644
--- a/tests/net/smtp/SMTPTransportTest.cpp
+++ b/tests/net/smtp/SMTPTransportTest.cpp
@@ -33,12 +33,16 @@
VMIME_TEST_SUITE_BEGIN(SMTPTransportTest)
VMIME_TEST_LIST_BEGIN
+/*
VMIME_TEST(testConnectToInvalidServer)
VMIME_TEST(testGreetingError)
VMIME_TEST(testMAILandRCPT)
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 +172,141 @@ VMIME_TEST_SUITE_BEGIN(SMTPTransportTest)
vmime::net::smtp::SMTPMessageSizeExceedsMaxLimitsException);
}
+ void testSMTPUTF8_available()
+ {
+ // Test with UTF8 sender
+ {
+ 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);
+ }
+
+ // Test with UTF8 recipient only
+ {
+ 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("[email protected]");
+
+ 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()
+ {
+ // Test with UTF8 sender
+ {
+ 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);
+ }
+
+ // Test with UTF8 recipient only
+ {
+ 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("[email protected]");
+
+ 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..b5af2f10 100644
--- a/tests/net/smtp/SMTPTransportTestUtils.hpp
+++ b/tests/net/smtp/SMTPTransportTestUtils.hpp
@@ -591,3 +591,193 @@ 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]>");
+ }
+ 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(
+ "MAIL",
+ std::string("MAIL FROM:<[email protected]> SMTPUTF8") == line
+ || std::string("MAIL FROM:<expé[email protected]> SMTPUTF8") == line
+ );
+ }
+ else
+ {
+ VASSERT(
+ "MAIL",
+ std::string("MAIL FROM:<[email protected]>") == line
+ || 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/parser/mailboxTest.cpp b/tests/parser/mailboxTest.cpp
index 6bf6670c..92fcd98f 100644
--- a/tests/parser/mailboxTest.cpp
+++ b/tests/parser/mailboxTest.cpp
@@ -30,6 +30,7 @@ VMIME_TEST_SUITE_BEGIN(mailboxTest)
VMIME_TEST(testParse)
VMIME_TEST(testEmptyEmailAddress)
VMIME_TEST(testSeparatorInComment)
+ VMIME_TEST(testAddressInName)
VMIME_TEST_LIST_END
@@ -145,5 +146,14 @@ VMIME_TEST_SUITE_BEGIN(mailboxTest)
VASSERT_EQ("email2", "[email protected]", mbox2->getEmail());
}
+ void testAddressInName()
+ {
+ vmime::mailbox mbox;
+
+ VASSERT_EQ("name", vmime::text("[email protected]"), mbox.getName());
+ VASSERT_EQ("email", "[email protected]", mbox.getEmail());
+ }
+
VMIME_TEST_SUITE_END
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);
}