aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVincent Richard <[email protected]>2013-06-24 13:32:40 +0000
committerVincent Richard <[email protected]>2013-06-24 13:32:40 +0000
commit895b07cae9741f44a1272b2f3875f8dd94763222 (patch)
tree6532de59e01676c78b423b5de5ebc1411c7da111
parentReturn after sending message when sending is supported. (diff)
downloadvmime-895b07cae9741f44a1272b2f3875f8dd94763222.tar.gz
vmime-895b07cae9741f44a1272b2f3875f8dd94763222.zip
Added support for SIZE SMTP extension (RFC-1870).
-rw-r--r--SConstruct4
-rw-r--r--src/body.cpp120
-rw-r--r--src/bodyPart.cpp6
-rw-r--r--src/component.cpp12
-rw-r--r--src/exception.cpp28
-rw-r--r--src/header.cpp6
-rw-r--r--src/headerField.cpp6
-rw-r--r--src/headerFieldValue.cpp44
-rw-r--r--src/net/smtp/SMTPCommand.cpp10
-rw-r--r--src/net/smtp/SMTPTransport.cpp57
-rw-r--r--src/utility/encoder/b64Encoder.cpp21
-rw-r--r--src/utility/encoder/defaultEncoder.cpp12
-rw-r--r--src/utility/encoder/qpEncoder.cpp21
-rw-r--r--src/utility/encoder/uuEncoder.cpp16
-rw-r--r--tests/net/smtp/SMTPCommandTest.cpp20
-rw-r--r--tests/net/smtp/SMTPTransportTest.cpp442
-rw-r--r--tests/net/smtp/SMTPTransportTestUtils.hpp615
-rw-r--r--tests/parser/messageTest.cpp57
-rw-r--r--tests/utility/encoder/b64EncoderTest.cpp8
-rw-r--r--tests/utility/encoder/encoderTestUtils.hpp22
-rw-r--r--tests/utility/encoder/qpEncoderTest.cpp8
-rw-r--r--vmime/body.hpp5
-rw-r--r--vmime/bodyPart.hpp2
-rw-r--r--vmime/component.hpp10
-rw-r--r--vmime/exception.hpp30
-rw-r--r--vmime/header.hpp2
-rw-r--r--vmime/headerField.hpp2
-rw-r--r--vmime/headerFieldValue.hpp1
-rw-r--r--vmime/net/smtp/SMTPCommand.hpp1
-rw-r--r--vmime/net/smtp/SMTPTransport.hpp4
-rw-r--r--vmime/stringContentHandler.hpp3
-rw-r--r--vmime/utility/encoder/b64Encoder.hpp3
-rw-r--r--vmime/utility/encoder/defaultEncoder.hpp3
-rw-r--r--vmime/utility/encoder/encoder.hpp18
-rw-r--r--vmime/utility/encoder/qpEncoder.hpp3
-rw-r--r--vmime/utility/encoder/uuEncoder.hpp3
36 files changed, 1173 insertions, 452 deletions
diff --git a/SConstruct b/SConstruct
index c810267f..27d708aa 100644
--- a/SConstruct
+++ b/SConstruct
@@ -80,7 +80,7 @@ libvmime_sources = [
'header.cpp', 'header.hpp',
'headerFieldFactory.cpp', 'headerFieldFactory.hpp',
'headerField.cpp', 'headerField.hpp',
- 'headerFieldValue.hpp',
+ 'headerFieldValue.cpp', 'headerFieldValue.hpp',
'htmlTextPart.cpp', 'htmlTextPart.hpp',
'mailbox.cpp', 'mailbox.hpp',
'mailboxField.cpp', 'mailboxField.hpp',
@@ -352,6 +352,7 @@ libvmime_tests = [
libvmimetest_common = [
'tests/testUtils.hpp',
+ 'tests/net/smtp/SMTPTransportTestUtils.hpp',
'tests/utility/encoder/encoderTestUtils.hpp'
]
@@ -374,6 +375,7 @@ libvmimetest_sources = [
'tests/parser/mediaTypeTest.cpp',
'tests/parser/messageIdTest.cpp',
'tests/parser/messageIdSequenceTest.cpp',
+ 'tests/parser/messageTest.cpp',
'tests/parser/pathTest.cpp',
'tests/parser/parameterTest.cpp',
'tests/parser/textTest.cpp',
diff --git a/src/body.cpp b/src/body.cpp
index 14c14fd6..d68254c7 100644
--- a/src/body.cpp
+++ b/src/body.cpp
@@ -30,6 +30,7 @@
#include "vmime/utility/random.hpp"
#include "vmime/utility/seekableInputStreamRegionAdapter.hpp"
+#include "vmime/utility/outputStreamAdapter.hpp"
#include "vmime/parserHelpers.hpp"
@@ -385,6 +386,40 @@ void body::parseImpl
}
+text body::getActualPrologText(const generationContext& ctx) const
+{
+ const string& prologText =
+ m_prologText.empty()
+ ? (isRootPart()
+ ? ctx.getPrologText()
+ : NULL_STRING
+ )
+ : m_prologText;
+
+ if (prologText.empty())
+ return text();
+ else
+ return text(prologText, vmime::charset("us-ascii"));
+}
+
+
+text body::getActualEpilogText(const generationContext& ctx) const
+{
+ const string& epilogText =
+ m_epilogText.empty()
+ ? (isRootPart()
+ ? ctx.getEpilogText()
+ : NULL_STRING
+ )
+ : m_epilogText;
+
+ if (epilogText.empty())
+ return text();
+ else
+ return text(epilogText, vmime::charset("us-ascii"));
+}
+
+
void body::generateImpl
(const generationContext& ctx, utility::outputStream& os,
const string::size_type /* curLinePos */, string::size_type* newLinePos) const
@@ -420,27 +455,12 @@ void body::generateImpl
}
}
- const string& prologText =
- m_prologText.empty()
- ? (isRootPart()
- ? ctx.getPrologText()
- : NULL_STRING
- )
- : m_prologText;
-
- const string& epilogText =
- m_epilogText.empty()
- ? (isRootPart()
- ? ctx.getEpilogText()
- : NULL_STRING
- )
- : m_epilogText;
-
- if (!prologText.empty())
- {
- text prolog(prologText, vmime::charset("us-ascii"));
+ const text prologText = getActualPrologText(ctx);
+ const text epilogText = getActualEpilogText(ctx);
- prolog.encodeAndFold(ctx, os, 0,
+ if (!prologText.isEmpty())
+ {
+ prologText.encodeAndFold(ctx, os, 0,
NULL, text::FORCE_NO_ENCODING | text::NO_NEW_LINE_SEQUENCE);
os << CRLF;
@@ -459,11 +479,9 @@ void body::generateImpl
os << "--" << CRLF;
- if (!epilogText.empty())
+ if (!epilogText.isEmpty())
{
- text epilog(epilogText, vmime::charset("us-ascii"));
-
- epilog.encodeAndFold(ctx, os, 0,
+ epilogText.encodeAndFold(ctx, os, 0,
NULL, text::FORCE_NO_ENCODING | text::NO_NEW_LINE_SEQUENCE);
os << CRLF;
@@ -481,6 +499,60 @@ void body::generateImpl
}
+utility::stream::size_type body::getGeneratedSize(const generationContext& ctx)
+{
+ // MIME-Multipart
+ if (getPartCount() != 0)
+ {
+ utility::stream::size_type size = 0;
+
+ // Size of parts and boundaries
+ for (size_t p = 0 ; p < getPartCount() ; ++p)
+ {
+ size += 100; // boundary, CRLF...
+ size += getPartAt(p)->getGeneratedSize(ctx);
+ }
+
+ // Size of prolog/epilog text
+ const text prologText = getActualPrologText(ctx);
+
+ if (!prologText.isEmpty())
+ {
+ std::ostringstream oss;
+ utility::outputStreamAdapter osa(oss);
+
+ prologText.encodeAndFold(ctx, osa, 0,
+ NULL, text::FORCE_NO_ENCODING | text::NO_NEW_LINE_SEQUENCE);
+
+ size += oss.str().size();
+ }
+
+ const text epilogText = getActualEpilogText(ctx);
+
+ if (!epilogText.isEmpty())
+ {
+ std::ostringstream oss;
+ utility::outputStreamAdapter osa(oss);
+
+ epilogText.encodeAndFold(ctx, osa, 0,
+ NULL, text::FORCE_NO_ENCODING | text::NO_NEW_LINE_SEQUENCE);
+
+ size += oss.str().size();
+ }
+
+ return size;
+ }
+ // Simple body
+ else
+ {
+ ref <utility::encoder::encoder> srcEncoder = m_contents->getEncoding().getEncoder();
+ ref <utility::encoder::encoder> dstEncoder = getEncoding().getEncoder();
+
+ return dstEncoder->getEncodedSize(srcEncoder->getDecodedSize(m_contents->getLength()));
+ }
+}
+
+
/*
RFC #1521, Page 32:
7.2.1. Multipart: The common syntax
diff --git a/src/bodyPart.cpp b/src/bodyPart.cpp
index 32544ba8..f63fd670 100644
--- a/src/bodyPart.cpp
+++ b/src/bodyPart.cpp
@@ -82,6 +82,12 @@ void bodyPart::generateImpl
}
+utility::stream::size_type bodyPart::getGeneratedSize(const generationContext& ctx)
+{
+ return m_header->getGeneratedSize(ctx) + 2 /* CRLF */ + m_body->getGeneratedSize(ctx);
+}
+
+
ref <component> bodyPart::clone() const
{
ref <bodyPart> p = vmime::create <bodyPart>();
diff --git a/src/component.cpp b/src/component.cpp
index 7226d0d2..d2138b60 100644
--- a/src/component.cpp
+++ b/src/component.cpp
@@ -233,5 +233,17 @@ void component::setParsedBounds(const string::size_type start, const string::siz
}
+utility::stream::size_type component::getGeneratedSize(const generationContext& ctx)
+{
+ std::vector <ref <component> > children = getChildComponents();
+ utility::stream::size_type totalSize = 0;
+
+ for (std::vector <ref <component> >::iterator it = children.begin() ; it != children.end() ; ++it)
+ totalSize += (*it)->getGeneratedSize(ctx);
+
+ return totalSize;
+}
+
+
} // vmime
diff --git a/src/exception.cpp b/src/exception.cpp
index f223a1bb..7dd40992 100644
--- a/src/exception.cpp
+++ b/src/exception.cpp
@@ -636,6 +636,34 @@ exception* invalid_folder_name::clone() const { return new invalid_folder_name(*
const char* invalid_folder_name::name() const throw() { return "invalid_folder_name"; }
+//
+// message_size_exceeds_max_limits
+//
+
+message_size_exceeds_max_limits::~message_size_exceeds_max_limits() throw() {}
+message_size_exceeds_max_limits::message_size_exceeds_max_limits(const string& error, const exception& other)
+ : net_exception(error.empty()
+ ? "Transport error: message size exceeds maximum server limits (permanent error)."
+ : error , other) {}
+
+exception* message_size_exceeds_max_limits::clone() const { return new message_size_exceeds_max_limits(*this); }
+const char* message_size_exceeds_max_limits::name() const throw() { return "message_size_exceeds_max_limits"; }
+
+
+//
+// message_size_exceeds_cur_limits
+//
+
+message_size_exceeds_cur_limits::~message_size_exceeds_cur_limits() throw() {}
+message_size_exceeds_cur_limits::message_size_exceeds_cur_limits(const string& error, const exception& other)
+ : net_exception(error.empty()
+ ? "Transport error: message size exceeds current server limits (temporary storage error)."
+ : error, other) {}
+
+exception* message_size_exceeds_cur_limits::clone() const { return new message_size_exceeds_cur_limits(*this); }
+const char* message_size_exceeds_cur_limits::name() const throw() { return "message_size_exceeds_cur_limits"; }
+
+
#endif // VMIME_HAVE_MESSAGING_FEATURES
diff --git a/src/header.cpp b/src/header.cpp
index 94f960e8..ec98976f 100644
--- a/src/header.cpp
+++ b/src/header.cpp
@@ -102,6 +102,12 @@ void header::generateImpl
}
+utility::stream::size_type header::getGeneratedSize(const generationContext& ctx)
+{
+ return component::getGeneratedSize(ctx) + 2 * m_fields.size() /* CRLF */;
+}
+
+
ref <component> header::clone() const
{
ref <header> hdr = vmime::create <header>();
diff --git a/src/headerField.cpp b/src/headerField.cpp
index 1d33dac1..7f24e176 100644
--- a/src/headerField.cpp
+++ b/src/headerField.cpp
@@ -297,6 +297,12 @@ void headerField::generateImpl
}
+utility::stream::size_type headerField::getGeneratedSize(const generationContext& ctx)
+{
+ return m_name.length() + 2 /* ": " */ + m_value->getGeneratedSize(ctx);
+}
+
+
const string headerField::getName() const
{
return m_name;
diff --git a/src/headerFieldValue.cpp b/src/headerFieldValue.cpp
new file mode 100644
index 00000000..19daf9f2
--- /dev/null
+++ b/src/headerFieldValue.cpp
@@ -0,0 +1,44 @@
+//
+// VMime library (http://www.vmime.org)
+// Copyright (C) 2002-2013 Vincent Richard <[email protected]>
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 3 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// Linking this library statically or dynamically with other modules is making
+// a combined work based on this library. Thus, the terms and conditions of
+// the GNU General Public License cover the whole combination.
+//
+
+#include "vmime/headerFieldValue.hpp"
+
+#include "vmime/utility/outputStreamAdapter.hpp"
+
+
+namespace vmime
+{
+
+
+utility::stream::size_type headerFieldValue::getGeneratedSize(const generationContext& ctx)
+{
+ std::ostringstream oss;
+ utility::outputStreamAdapter osa(oss);
+
+ generate(ctx, osa);
+
+ return oss.str().length();
+}
+
+
+} // vmime
diff --git a/src/net/smtp/SMTPCommand.cpp b/src/net/smtp/SMTPCommand.cpp
index 9813c4f5..e40797f2 100644
--- a/src/net/smtp/SMTPCommand.cpp
+++ b/src/net/smtp/SMTPCommand.cpp
@@ -89,6 +89,13 @@ ref <SMTPCommand> SMTPCommand::STARTTLS()
// static
ref <SMTPCommand> SMTPCommand::MAIL(const mailbox& mbox, const bool utf8)
{
+ return MAIL(mbox, utf8, 0);
+}
+
+
+// static
+ref <SMTPCommand> SMTPCommand::MAIL(const mailbox& mbox, const bool utf8, const unsigned long size)
+{
std::ostringstream cmd;
cmd.imbue(std::locale::classic());
cmd << "MAIL FROM:<";
@@ -108,6 +115,9 @@ ref <SMTPCommand> SMTPCommand::MAIL(const mailbox& mbox, const bool utf8)
if (utf8)
cmd << " SMTPUTF8";
+ if (size != 0)
+ cmd << " SIZE=" << size;
+
return createCommand(cmd.str());
}
diff --git a/src/net/smtp/SMTPTransport.cpp b/src/net/smtp/SMTPTransport.cpp
index 40b6375c..46e47f35 100644
--- a/src/net/smtp/SMTPTransport.cpp
+++ b/src/net/smtp/SMTPTransport.cpp
@@ -158,7 +158,8 @@ void SMTPTransport::noop()
void SMTPTransport::sendEnvelope
(const mailbox& expeditor, const mailboxList& recipients,
- const mailbox& sender, bool sendDATACommand)
+ const mailbox& sender, bool sendDATACommand,
+ const utility::stream::size_type size)
{
// If no recipient/expeditor was found, throw an exception
if (recipients.isEmpty())
@@ -179,11 +180,12 @@ void SMTPTransport::sendEnvelope
// Emit the "MAIL" command
const bool hasSMTPUTF8 = m_connection->hasExtension("SMTPUTF8");
+ const bool hasSize = m_connection->hasExtension("SIZE");
if (!sender.isEmpty())
- commands->addCommand(SMTPCommand::MAIL(sender, hasSMTPUTF8));
+ commands->addCommand(SMTPCommand::MAIL(sender, hasSMTPUTF8, hasSize ? size : 0));
else
- commands->addCommand(SMTPCommand::MAIL(expeditor, hasSMTPUTF8));
+ commands->addCommand(SMTPCommand::MAIL(expeditor, hasSMTPUTF8, hasSize ? size : 0));
// Now, we will need to reset next time
m_needReset = true;
@@ -216,8 +218,26 @@ void SMTPTransport::sendEnvelope
if ((resp = m_connection->readResponse())->getCode() != 250)
{
- disconnect();
- throw exceptions::command_error(commands->getLastCommandSent()->getText(), resp->getText());
+ // SIZE extension: insufficient system storage
+ if (resp->getCode() == 452)
+ {
+ disconnect();
+ throw exceptions::message_size_exceeds_cur_limits
+ (commands->getLastCommandSent()->getText(), resp->getText());
+ }
+ // SIZE extension: message size exceeds fixed maximum message size
+ else if (resp->getCode() == 552)
+ {
+ disconnect();
+ throw exceptions::message_size_exceeds_max_limits
+ (commands->getLastCommandSent()->getText(), resp->getText());
+ }
+ // Other error
+ else
+ {
+ disconnect();
+ throw exceptions::command_error(commands->getLastCommandSent()->getText(), resp->getText());
+ }
}
// Read responses for "RCPT TO" commands
@@ -230,8 +250,26 @@ void SMTPTransport::sendEnvelope
if (resp->getCode() != 250 &&
resp->getCode() != 251)
{
- disconnect();
- throw exceptions::command_error(commands->getLastCommandSent()->getText(), resp->getText());
+ // SIZE extension: insufficient system storage
+ if (resp->getCode() == 452)
+ {
+ disconnect();
+ throw exceptions::message_size_exceeds_cur_limits
+ (commands->getLastCommandSent()->getText(), resp->getText());
+ }
+ // SIZE extension: message size exceeds fixed maximum message size
+ else if (resp->getCode() == 552)
+ {
+ disconnect();
+ throw exceptions::message_size_exceeds_max_limits
+ (commands->getLastCommandSent()->getText(), resp->getText());
+ }
+ // Other error
+ else
+ {
+ disconnect();
+ throw exceptions::command_error(commands->getLastCommandSent()->getText(), resp->getText());
+ }
}
}
@@ -258,7 +296,7 @@ void SMTPTransport::send
throw exceptions::not_connected();
// Send message envelope
- sendEnvelope(expeditor, recipients, sender, /* sendDATACommand */ true);
+ sendEnvelope(expeditor, recipients, sender, /* sendDATACommand */ true, size);
// Send the message data
// Stream copy with "\n." to "\n.." transformation
@@ -312,7 +350,8 @@ void SMTPTransport::send
}
// Send message envelope
- sendEnvelope(expeditor, recipients, sender, /* sendDATACommand */ false);
+ sendEnvelope(expeditor, recipients, sender,
+ /* sendDATACommand */ false, msg->getGeneratedSize(ctx));
// Send the message by chunks
SMTPChunkingOutputStreamAdapter chunkStream(m_connection);
diff --git a/src/utility/encoder/b64Encoder.cpp b/src/utility/encoder/b64Encoder.cpp
index d67a91ac..20e16b98 100644
--- a/src/utility/encoder/b64Encoder.cpp
+++ b/src/utility/encoder/b64Encoder.cpp
@@ -304,6 +304,27 @@ utility::stream::size_type b64Encoder::decode(utility::inputStream& in,
}
+utility::stream::size_type b64Encoder::getEncodedSize(const utility::stream::size_type n) const
+{
+ const string::size_type propMaxLineLength =
+ getProperties().getProperty <string::size_type>("maxlinelength", static_cast <string::size_type>(-1));
+
+ const bool cutLines = (propMaxLineLength != static_cast <string::size_type>(-1));
+ const string::size_type maxLineLength = std::min(propMaxLineLength, static_cast <string::size_type>(76));
+
+ return (n * 4) / 3 // 3 bytes of input provide 4 bytes of output
+ + (cutLines ? (n / maxLineLength) * 2 : 0) // CRLF (2 bytes) for each line.
+ + 4; // padding
+}
+
+
+utility::stream::size_type b64Encoder::getDecodedSize(const utility::stream::size_type n) const
+{
+ // 4 bytes of input provide 3 bytes of output
+ return (n * 3) / 4;
+}
+
+
} // encoder
} // utility
} // vmime
diff --git a/src/utility/encoder/defaultEncoder.cpp b/src/utility/encoder/defaultEncoder.cpp
index 3a0656c9..95e531cd 100644
--- a/src/utility/encoder/defaultEncoder.cpp
+++ b/src/utility/encoder/defaultEncoder.cpp
@@ -70,6 +70,18 @@ utility::stream::size_type defaultEncoder::decode(utility::inputStream& in,
}
+utility::stream::size_type defaultEncoder::getEncodedSize(const utility::stream::size_type n) const
+{
+ return n;
+}
+
+
+utility::stream::size_type defaultEncoder::getDecodedSize(const utility::stream::size_type n) const
+{
+ return n;
+}
+
+
} // encoder
} // utility
} // vmime
diff --git a/src/utility/encoder/qpEncoder.cpp b/src/utility/encoder/qpEncoder.cpp
index d519de14..1768818c 100644
--- a/src/utility/encoder/qpEncoder.cpp
+++ b/src/utility/encoder/qpEncoder.cpp
@@ -532,6 +532,27 @@ utility::stream::size_type qpEncoder::decode(utility::inputStream& in,
}
+utility::stream::size_type qpEncoder::getEncodedSize(const utility::stream::size_type n) const
+{
+ const string::size_type propMaxLineLength =
+ getProperties().getProperty <string::size_type>("maxlinelength", static_cast <string::size_type>(-1));
+
+ const bool cutLines = (propMaxLineLength != static_cast <string::size_type>(-1));
+ const string::size_type maxLineLength = std::min(propMaxLineLength, static_cast <string::size_type>(74));
+
+ // Worst cast: 1 byte of input provide 3 bytes of output
+ // Count CRLF (2 bytes) for each line.
+ return n * 3 + (cutLines ? (n / maxLineLength) * 2 : 0);
+}
+
+
+utility::stream::size_type qpEncoder::getDecodedSize(const utility::stream::size_type n) const
+{
+ // Worst case: 1 byte of input equals 1 byte of output
+ return n;
+}
+
+
} // encoder
} // utility
} // vmime
diff --git a/src/utility/encoder/uuEncoder.cpp b/src/utility/encoder/uuEncoder.cpp
index 00d90cee..3f751d3b 100644
--- a/src/utility/encoder/uuEncoder.cpp
+++ b/src/utility/encoder/uuEncoder.cpp
@@ -326,6 +326,22 @@ utility::stream::size_type uuEncoder::decode(utility::inputStream& in,
}
+utility::stream::size_type uuEncoder::getEncodedSize(const utility::stream::size_type n) const
+{
+ // 3 bytes of input provide 4 bytes of output.
+ // Count CRLF (2 bytes) for each line of 45 characters.
+ // Also reserve some space for header and footer.
+ return 200 + n * 3 + (n / 45) * 2;
+}
+
+
+utility::stream::size_type uuEncoder::getDecodedSize(const utility::stream::size_type n) const
+{
+ // 4 bytes of input provide 3 bytes of output
+ return (n * 3) / 4;
+}
+
+
} // encoder
} // utility
} // vmime
diff --git a/tests/net/smtp/SMTPCommandTest.cpp b/tests/net/smtp/SMTPCommandTest.cpp
index ce9e7ce5..9a2c90fc 100644
--- a/tests/net/smtp/SMTPCommandTest.cpp
+++ b/tests/net/smtp/SMTPCommandTest.cpp
@@ -41,6 +41,8 @@ VMIME_TEST_SUITE_BEGIN(SMTPCommandTest)
VMIME_TEST(testMAIL)
VMIME_TEST(testMAIL_Encoded)
VMIME_TEST(testMAIL_UTF8)
+ VMIME_TEST(testMAIL_SIZE)
+ VMIME_TEST(testMAIL_SIZE_UTF8)
VMIME_TEST(testRCPT)
VMIME_TEST(testRCPT_Encoded)
VMIME_TEST(testRCPT_UTF8)
@@ -127,6 +129,24 @@ VMIME_TEST_SUITE_BEGIN(SMTPCommandTest)
VASSERT_EQ("Text", "MAIL FROM:<mailtest@例え.テスト> SMTPUTF8", cmd->getText());
}
+ void testMAIL_SIZE()
+ {
+ vmime::ref <SMTPCommand> cmd = SMTPCommand::MAIL
+ (vmime::mailbox("[email protected]"), false, 123456789);
+
+ VASSERT_NOT_NULL("Not null", cmd);
+ VASSERT_EQ("Text", "MAIL FROM:<[email protected]> SIZE=123456789", cmd->getText());
+ }
+
+ void testMAIL_SIZE_UTF8()
+ {
+ vmime::ref <SMTPCommand> cmd = SMTPCommand::MAIL
+ (vmime::mailbox(vmime::emailAddress("mailtest", "例え.テスト")), true, 123456789);
+
+ VASSERT_NOT_NULL("Not null", cmd);
+ VASSERT_EQ("Text", "MAIL FROM:<mailtest@例え.テスト> SMTPUTF8 SIZE=123456789", cmd->getText());
+ }
+
void testRCPT()
{
vmime::ref <SMTPCommand> cmd = SMTPCommand::RCPT(vmime::mailbox("[email protected]"), false);
diff --git a/tests/net/smtp/SMTPTransportTest.cpp b/tests/net/smtp/SMTPTransportTest.cpp
index c91ffe90..70aaa4fd 100644
--- a/tests/net/smtp/SMTPTransportTest.cpp
+++ b/tests/net/smtp/SMTPTransportTest.cpp
@@ -26,11 +26,7 @@
#include "vmime/net/smtp/SMTPTransport.hpp"
#include "vmime/net/smtp/SMTPChunkingOutputStreamAdapter.hpp"
-
-class greetingErrorSMTPTestSocket;
-class MAILandRCPTSMTPTestSocket;
-class chunkingSMTPTestSocket;
-class SMTPTestMessage;
+#include "SMTPTransportTestUtils.hpp"
VMIME_TEST_SUITE_BEGIN(SMTPTransportTest)
@@ -39,6 +35,8 @@ VMIME_TEST_SUITE_BEGIN(SMTPTransportTest)
VMIME_TEST(testGreetingError)
VMIME_TEST(testMAILandRCPT)
VMIME_TEST(testChunking)
+ VMIME_TEST(testSize_Chunking)
+ VMIME_TEST(testSize_NoChunking)
VMIME_TEST_LIST_END
@@ -109,427 +107,59 @@ VMIME_TEST_SUITE_BEGIN(SMTPTransportTest)
tr->send(msg, exp, recips);
}
-VMIME_TEST_SUITE_END
-
-
-/** Accepts connection and fails on greeting.
- */
-class greetingErrorSMTPTestSocket : public lineBasedTestSocket
-{
-public:
-
- void onConnected()
- {
- localSend("421 test.vmime.org Service not available, closing transmission channel\r\n");
- disconnect();
- }
-
- void processCommand()
- {
- if (!haveMoreLines())
- return;
-
- getNextLine();
-
- localSend("502 Command not implemented\r\n");
- processCommand();
- }
-};
-
-
-/** SMTP test server 1.
- *
- * Test send().
- * Ensure MAIL and RCPT commands are sent correctly.
- */
-class MAILandRCPTSMTPTestSocket : public lineBasedTestSocket
-{
-public:
-
- MAILandRCPTSMTPTestSocket()
+ void testSize_Chunking()
{
- m_recipients.insert("[email protected]");
- m_recipients.insert("[email protected]");
- 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()
- {
- 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")
- {
- 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("The MAIL command must be sent only one time", !m_mailSent);
-
- VASSERT_EQ("MAIL", std::string("MAIL FROM:<[email protected]>"), line);
-
- localSend("250 OK\r\n");
-
- m_mailSent = true;
- }
- else if (cmd == "RCPT")
- {
- const vmime::string::size_type lt = line.find('<');
- const vmime::string::size_type gt = line.find('>');
-
- VASSERT("RCPT <", lt != vmime::string::npos);
- VASSERT("RCPT >", gt != vmime::string::npos);
- VASSERT("RCPT ><", gt >= lt);
-
- const vmime::string recip = vmime::string
- (line.begin() + lt + 1, line.begin() + gt);
-
- std::set <vmime::string>::iterator it =
- m_recipients.find(recip);
-
- VASSERT(std::string("Recipient not found: '") + recip + "'",
- it != m_recipients.end());
-
- 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")
- {
- 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_recipients;
-
- std::string m_msgData;
-
- bool m_ehloSent, m_heloSent, m_mailSent, m_rcptSent,
- m_dataSent, m_quitSent;
-};
-
+ vmime::ref <vmime::net::session> session =
+ vmime::create <vmime::net::session>();
+ vmime::ref <vmime::net::transport> tr = session->getTransport
+ (vmime::utility::url("smtp://localhost"));
-/** SMTP test server 2.
- *
- * Test CHUNKING extension/BDAT command.
- */
-class chunkingSMTPTestSocket : public testSocket
-{
-public:
+ tr->setSocketFactory(vmime::create <testSocketFactory <bigMessageSMTPTestSocket <true> > >());
+ tr->setTimeoutHandlerFactory(vmime::create <testTimeoutHandlerFactory>());
- chunkingSMTPTestSocket()
- {
- m_state = STATE_NOT_CONNECTED;
- m_bdatChunkCount = 0;
- m_ehloSent = m_mailSent = m_rcptSent = m_quitSent = false;
- }
+ tr->connect();
- ~chunkingSMTPTestSocket()
- {
- VASSERT_EQ("BDAT chunk count", 3, m_bdatChunkCount);
- VASSERT("Client must send the QUIT command", m_quitSent);
- }
+ VASSERT("Test server should report it supports the SIZE extension!",
+ tr.dynamicCast <vmime::net::smtp::SMTPTransport>()->getConnection()->hasExtension("SIZE"));
- void onConnected()
- {
- localSend("220 test.vmime.org Service ready\r\n");
- processCommand();
+ vmime::mailbox exp("[email protected]");
- m_state = STATE_COMMAND;
- }
+ vmime::mailboxList recips;
+ recips.appendMailbox(vmime::create <vmime::mailbox>("[email protected]"));
- 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();
- }
+ vmime::ref <vmime::message> msg = vmime::create <SMTPBigTestMessage4MB>();
- 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")
- {
- VASSERT("The MAIL command must be sent only one time", !m_mailSent);
-
- 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();
+ VASSERT_THROW("Connection", tr->send(msg, exp, recips),
+ vmime::exceptions::message_size_exceeds_max_limits);
}
-private:
-
- enum State
+ void testSize_NoChunking()
{
- STATE_NOT_CONNECTED,
- STATE_COMMAND,
- STATE_DATA
- };
+ vmime::ref <vmime::net::session> session =
+ vmime::create <vmime::net::session>();
- int m_state;
- int m_bdatChunkCount;
- int m_bdatChunkSize, m_bdatChunkReceived;
+ vmime::ref <vmime::net::transport> tr = session->getTransport
+ (vmime::utility::url("smtp://localhost"));
- bool m_ehloSent, m_mailSent, m_rcptSent, m_quitSent;
-};
+ tr->setSocketFactory(vmime::create <testSocketFactory <bigMessageSMTPTestSocket <false> > >());
+ tr->setTimeoutHandlerFactory(vmime::create <testTimeoutHandlerFactory>());
+ tr->connect();
-class SMTPTestMessage : public vmime::message
-{
-public:
+ VASSERT("Test server should report it supports the SIZE extension!",
+ tr.dynamicCast <vmime::net::smtp::SMTPTransport>()->getConnection()->hasExtension("SIZE"));
- 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;
+ vmime::mailbox exp("[email protected]");
- 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'));
- }
+ vmime::mailboxList recips;
+ recips.appendMailbox(vmime::create <vmime::mailbox>("[email protected]"));
- return chunks;
- }
+ vmime::ref <vmime::message> msg = vmime::create <SMTPBigTestMessage4MB>();
- 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());
- }
+ VASSERT_THROW("Connection", tr->send(msg, exp, recips),
+ vmime::exceptions::message_size_exceeds_max_limits);
}
-};
+VMIME_TEST_SUITE_END
diff --git a/tests/net/smtp/SMTPTransportTestUtils.hpp b/tests/net/smtp/SMTPTransportTestUtils.hpp
new file mode 100644
index 00000000..b74f9783
--- /dev/null
+++ b/tests/net/smtp/SMTPTransportTestUtils.hpp
@@ -0,0 +1,615 @@
+//
+// VMime library (http://www.vmime.org)
+// Copyright (C) 2002-2013 Vincent Richard <[email protected]>
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 3 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// Linking this library statically or dynamically with other modules is making
+// a combined work based on this library. Thus, the terms and conditions of
+// the GNU General Public License cover the whole combination.
+//
+
+
+/** Accepts connection and fails on greeting.
+ */
+class greetingErrorSMTPTestSocket : public lineBasedTestSocket
+{
+public:
+
+ void onConnected()
+ {
+ localSend("421 test.vmime.org Service not available, closing transmission channel\r\n");
+ disconnect();
+ }
+
+ void processCommand()
+ {
+ if (!haveMoreLines())
+ return;
+
+ getNextLine();
+
+ localSend("502 Command not implemented\r\n");
+ processCommand();
+ }
+};
+
+
+/** SMTP test server 1.
+ *
+ * Test send().
+ * Ensure MAIL and RCPT commands are sent correctly.
+ */
+class MAILandRCPTSMTPTestSocket : public lineBasedTestSocket
+{
+public:
+
+ MAILandRCPTSMTPTestSocket()
+ {
+ m_recipients.insert("[email protected]");
+ m_recipients.insert("[email protected]");
+ 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()
+ {
+ 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")
+ {
+ 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("The MAIL command must be sent only one time", !m_mailSent);
+
+ VASSERT_EQ("MAIL", std::string("MAIL FROM:<[email protected]>"), line);
+
+ localSend("250 OK\r\n");
+
+ m_mailSent = true;
+ }
+ else if (cmd == "RCPT")
+ {
+ const vmime::string::size_type lt = line.find('<');
+ const vmime::string::size_type gt = line.find('>');
+
+ VASSERT("RCPT <", lt != vmime::string::npos);
+ VASSERT("RCPT >", gt != vmime::string::npos);
+ VASSERT("RCPT ><", gt >= lt);
+
+ const vmime::string recip = vmime::string
+ (line.begin() + lt + 1, line.begin() + gt);
+
+ std::set <vmime::string>::iterator it =
+ m_recipients.find(recip);
+
+ VASSERT(std::string("Recipient not found: '") + recip + "'",
+ it != m_recipients.end());
+
+ 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")
+ {
+ 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_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")
+ {
+ VASSERT("The MAIL command must be sent only one time", !m_mailSent);
+
+ 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());
+ }
+ }
+};
+
+
+
+/** SMTP test server 3.
+ *
+ * Test SIZE extension.
+ */
+template <bool WITH_CHUNKING>
+class bigMessageSMTPTestSocket : public testSocket
+{
+public:
+
+ bigMessageSMTPTestSocket()
+ {
+ m_state = STATE_NOT_CONNECTED;
+ m_bdatChunkCount = 0;
+ m_ehloSent = m_mailSent = m_rcptSent = m_quitSent = false;
+ }
+
+ ~bigMessageSMTPTestSocket()
+ {
+ 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");
+
+ if (WITH_CHUNKING)
+ localSend("250-CHUNKING\r\n");
+
+ localSend("250 SIZE 1000000\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("The MAIL command must be sent only one time", !m_mailSent);
+
+ std::string address;
+ iss >> address;
+
+ VASSERT_EQ("MAIL/address", "FROM:<[email protected]>", address);
+
+ std::string option;
+ iss >> option;
+
+ VASSERT_EQ("MAIL/size", "SIZE=4194304", option);
+
+ localSend("552 Channel size limit exceeded\r\n");
+
+ m_mailSent = true;
+ }
+ 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
+ {
+ VASSERT("No other command should be sent", false);
+
+ 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;
+};
+
+
+template <unsigned long SIZE>
+class SMTPBigTestMessage : public vmime::message
+{
+public:
+
+ vmime::utility::stream::size_type getGeneratedSize
+ (const vmime::generationContext& /* ctx */)
+ {
+ return SIZE;
+ }
+
+ 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 = SIZE ; i < n ; ++i)
+ outputStream.write("X", 1);
+ }
+};
+
+typedef SMTPBigTestMessage <4194304> SMTPBigTestMessage4MB;
diff --git a/tests/parser/messageTest.cpp b/tests/parser/messageTest.cpp
new file mode 100644
index 00000000..56605e39
--- /dev/null
+++ b/tests/parser/messageTest.cpp
@@ -0,0 +1,57 @@
+//
+// VMime library (http://www.vmime.org)
+// Copyright (C) 2002-2013 Vincent Richard <[email protected]>
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 3 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// Linking this library statically or dynamically with other modules is making
+// a combined work based on this library. Thus, the terms and conditions of
+// the GNU General Public License cover the whole combination.
+//
+
+#include "tests/testUtils.hpp"
+
+
+VMIME_TEST_SUITE_BEGIN(messageTest)
+
+ VMIME_TEST_LIST_BEGIN
+ VMIME_TEST(testGetGeneratedSize)
+ VMIME_TEST_LIST_END
+
+
+ void testGetGeneratedSize()
+ {
+ vmime::generationContext ctx;
+
+ vmime::ref <vmime::message> msg = vmime::create <vmime::message>();
+ msg->getHeader()->getField("Foo")->setValue(vmime::string("bar"));
+
+ vmime::htmlTextPart textPart;
+ textPart.setPlainText(vmime::create <vmime::stringContentHandler>("Foo bar bazé foo foo foo"));
+ textPart.setText(vmime::create <vmime::stringContentHandler>("Foo bar <strong>bazé</strong> foo foo foo"));
+ textPart.generateIn(msg, msg);
+
+ // Estimated/computed generated size must be greater than the actual generated size
+ const unsigned long genSize = msg->getGeneratedSize(ctx);
+ const unsigned long actualSize = msg->generate().length();
+
+ std::ostringstream oss;
+ oss << "estimated size (" << genSize << ") >= actual size (" << actualSize << ")";
+
+ VASSERT(oss.str(), genSize >= actualSize);
+ }
+
+VMIME_TEST_SUITE_END
+
diff --git a/tests/utility/encoder/b64EncoderTest.cpp b/tests/utility/encoder/b64EncoderTest.cpp
index 589afaba..fa6fd766 100644
--- a/tests/utility/encoder/b64EncoderTest.cpp
+++ b/tests/utility/encoder/b64EncoderTest.cpp
@@ -138,6 +138,14 @@ VMIME_TEST_SUITE_BEGIN(b64EncoderTest)
encode("base64",
encode("base64",
encode("base64", decoded)))))))));
+
+ VASSERT(oss.str() + "encoded size",
+ getEncoder("base64")->getEncodedSize(decoded.length())
+ >= encode("base64", decoded).length());
+
+ VASSERT(oss.str() + "decoded size",
+ getEncoder("base64")->getDecodedSize(encoded.length())
+ >= decode("base64", encoded).length());
}
}
diff --git a/tests/utility/encoder/encoderTestUtils.hpp b/tests/utility/encoder/encoderTestUtils.hpp
index dd2484ed..0eb93871 100644
--- a/tests/utility/encoder/encoderTestUtils.hpp
+++ b/tests/utility/encoder/encoderTestUtils.hpp
@@ -22,9 +22,9 @@
//
-// Encoding helper function
-static const vmime::string encode(const vmime::string& name, const vmime::string& in,
- int maxLineLength = 0, const vmime::propertySet props = vmime::propertySet())
+// Helper function to obtain an encoder given its name
+static vmime::ref <vmime::utility::encoder::encoder> getEncoder(const vmime::string& name,
+ int maxLineLength = 0, const vmime::propertySet props = vmime::propertySet())
{
vmime::ref <vmime::utility::encoder::encoder> enc =
vmime::utility::encoder::encoderFactory::getInstance()->create(name);
@@ -34,6 +34,16 @@ static const vmime::string encode(const vmime::string& name, const vmime::string
if (maxLineLength != 0)
enc->getProperties()["maxlinelength"] = maxLineLength;
+ return enc;
+}
+
+
+// Encoding helper function
+static const vmime::string encode(const vmime::string& name, const vmime::string& in,
+ int maxLineLength = 0, const vmime::propertySet props = vmime::propertySet())
+{
+ vmime::ref <vmime::utility::encoder::encoder> enc = getEncoder(name, maxLineLength, props);
+
vmime::utility::inputStreamStringAdapter vin(in);
std::ostringstream out;
@@ -48,11 +58,7 @@ static const vmime::string encode(const vmime::string& name, const vmime::string
// Decoding helper function
static const vmime::string decode(const vmime::string& name, const vmime::string& in, int maxLineLength = 0)
{
- vmime::ref <vmime::utility::encoder::encoder> enc =
- vmime::utility::encoder::encoderFactory::getInstance()->create(name);
-
- if (maxLineLength != 0)
- enc->getProperties()["maxlinelength"] = maxLineLength;
+ vmime::ref <vmime::utility::encoder::encoder> enc = getEncoder(name, maxLineLength);
vmime::utility::inputStreamStringAdapter vin(in);
diff --git a/tests/utility/encoder/qpEncoderTest.cpp b/tests/utility/encoder/qpEncoderTest.cpp
index 790c5fb3..50183488 100644
--- a/tests/utility/encoder/qpEncoderTest.cpp
+++ b/tests/utility/encoder/qpEncoderTest.cpp
@@ -134,6 +134,14 @@ VMIME_TEST_SUITE_BEGIN(qpEncoderTest)
encode("quoted-printable",
encode("quoted-printable",
encode("quoted-printable", decoded)))))))));
+
+ VASSERT(oss.str() + "encoded size",
+ getEncoder("quoted-printable")->getEncodedSize(decoded.length())
+ >= encode("quoted-printable", decoded).length());
+
+ VASSERT(oss.str() + "decoded size",
+ getEncoder("quoted-printable")->getDecodedSize(encoded.length())
+ >= decode("quoted-printable", encoded).length());
}
}
diff --git a/vmime/body.hpp b/vmime/body.hpp
index 50d67807..6869bb70 100644
--- a/vmime/body.hpp
+++ b/vmime/body.hpp
@@ -280,8 +280,13 @@ public:
const std::vector <ref <component> > getChildComponents();
+ utility::stream::size_type getGeneratedSize(const generationContext& ctx);
+
private:
+ text getActualPrologText(const generationContext& ctx) const;
+ text getActualEpilogText(const generationContext& ctx) const;
+
void setParentPart(ref <bodyPart> parent);
diff --git a/vmime/bodyPart.hpp b/vmime/bodyPart.hpp
index e54f7c0a..62365b8b 100644
--- a/vmime/bodyPart.hpp
+++ b/vmime/bodyPart.hpp
@@ -103,6 +103,8 @@ public:
const std::vector <ref <component> > getChildComponents();
+ utility::stream::size_type getGeneratedSize(const generationContext& ctx);
+
private:
ref <header> m_header;
diff --git a/vmime/component.hpp b/vmime/component.hpp
index 6a7ff43c..4c6078f3 100644
--- a/vmime/component.hpp
+++ b/vmime/component.hpp
@@ -209,6 +209,16 @@ public:
*/
virtual const std::vector <ref <component> > getChildComponents() = 0;
+ /** Get the number of bytes that will be used by this component when
+ * it is generated. This may be a heuristically-derived estimate,
+ * but such an estimated size should always be larger than the actual
+ * generated size.
+ *
+ * @param ctx generation context
+ * @return component size when generated
+ */
+ virtual utility::stream::size_type getGeneratedSize(const generationContext& ctx);
+
protected:
void setParsedBounds(const utility::stream::size_type start, const utility::stream::size_type end);
diff --git a/vmime/exception.hpp b/vmime/exception.hpp
index b3bb02b0..f11079c5 100644
--- a/vmime/exception.hpp
+++ b/vmime/exception.hpp
@@ -757,6 +757,36 @@ public:
};
+/** Transport error: message size exceeds maximum server limits.
+ */
+
+class VMIME_EXPORT message_size_exceeds_max_limits : public net_exception
+{
+public:
+
+ message_size_exceeds_max_limits(const string& error = "", const exception& other = NO_EXCEPTION);
+ ~message_size_exceeds_max_limits() throw();
+
+ exception* clone() const;
+ const char* name() const throw();
+};
+
+
+/** Transport error: message size exceeds current server limits.
+ */
+
+class VMIME_EXPORT message_size_exceeds_cur_limits : public net_exception
+{
+public:
+
+ message_size_exceeds_cur_limits(const string& error = "", const exception& other = NO_EXCEPTION);
+ ~message_size_exceeds_cur_limits() throw();
+
+ exception* clone() const;
+ const char* name() const throw();
+};
+
+
#endif // VMIME_HAVE_MESSAGING_FEATURES
diff --git a/vmime/header.hpp b/vmime/header.hpp
index 9e3a2f1e..0758e775 100644
--- a/vmime/header.hpp
+++ b/vmime/header.hpp
@@ -230,6 +230,8 @@ public:
const std::vector <ref <component> > getChildComponents();
+ utility::stream::size_type getGeneratedSize(const generationContext& ctx);
+
private:
std::vector <ref <headerField> > m_fields;
diff --git a/vmime/headerField.hpp b/vmime/headerField.hpp
index 8f8c2a1c..5276c84a 100644
--- a/vmime/headerField.hpp
+++ b/vmime/headerField.hpp
@@ -141,6 +141,8 @@ public:
const string::size_type end,
string::size_type* newPosition = NULL);
+ utility::stream::size_type getGeneratedSize(const generationContext& ctx);
+
protected:
void parseImpl
diff --git a/vmime/headerFieldValue.hpp b/vmime/headerFieldValue.hpp
index 1950abef..2a04a823 100644
--- a/vmime/headerFieldValue.hpp
+++ b/vmime/headerFieldValue.hpp
@@ -41,6 +41,7 @@ class VMIME_EXPORT headerFieldValue : public component
{
public:
+ utility::stream::size_type getGeneratedSize(const generationContext& ctx);
};
diff --git a/vmime/net/smtp/SMTPCommand.hpp b/vmime/net/smtp/SMTPCommand.hpp
index dcca5f2a..c4001f85 100644
--- a/vmime/net/smtp/SMTPCommand.hpp
+++ b/vmime/net/smtp/SMTPCommand.hpp
@@ -64,6 +64,7 @@ public:
static ref <SMTPCommand> AUTH(const string& mechName);
static ref <SMTPCommand> STARTTLS();
static ref <SMTPCommand> MAIL(const mailbox& mbox, const bool utf8);
+ static ref <SMTPCommand> MAIL(const mailbox& mbox, const bool utf8, const unsigned long size);
static ref <SMTPCommand> RCPT(const mailbox& mbox, const bool utf8);
static ref <SMTPCommand> RSET();
static ref <SMTPCommand> DATA();
diff --git a/vmime/net/smtp/SMTPTransport.hpp b/vmime/net/smtp/SMTPTransport.hpp
index 3629d5d7..962f4406 100644
--- a/vmime/net/smtp/SMTPTransport.hpp
+++ b/vmime/net/smtp/SMTPTransport.hpp
@@ -99,12 +99,14 @@ private:
* @param recipients list of recipient mailboxes
* @param sender envelope sender (if empty, expeditor will be used)
* @param sendDATACommand if true, the DATA command will be sent
+ * @param size message size, in bytes (or 0, if not known)
*/
void sendEnvelope
(const mailbox& expeditor,
const mailboxList& recipients,
const mailbox& sender,
- bool sendDATACommand);
+ bool sendDATACommand,
+ const utility::stream::size_type size);
ref <SMTPConnection> m_connection;
diff --git a/vmime/stringContentHandler.hpp b/vmime/stringContentHandler.hpp
index 1ab63fcf..20a29157 100644
--- a/vmime/stringContentHandler.hpp
+++ b/vmime/stringContentHandler.hpp
@@ -58,9 +58,6 @@ public:
// encoding/decoding will be performed on generate()/extract()). Note that the
// data may be re-encoded (that is, decoded and encoded) if the encoding passed
// to generate() is different from this one...
- //
- // The 'length' parameter is optional (user-defined). You can pass 0 if you want,
- // VMime does not make use of it.
void setData(const utility::stringProxy& str, const vmime::encoding& enc = NO_ENCODING);
void setData(const string& buffer, const vmime::encoding& enc = NO_ENCODING);
void setData(const string& buffer, const string::size_type start, const string::size_type end, const vmime::encoding& enc = NO_ENCODING);
diff --git a/vmime/utility/encoder/b64Encoder.hpp b/vmime/utility/encoder/b64Encoder.hpp
index fec10556..e2bb5780 100644
--- a/vmime/utility/encoder/b64Encoder.hpp
+++ b/vmime/utility/encoder/b64Encoder.hpp
@@ -47,6 +47,9 @@ public:
const std::vector <string> getAvailableProperties() const;
+ utility::stream::size_type getEncodedSize(const utility::stream::size_type n) const;
+ utility::stream::size_type getDecodedSize(const utility::stream::size_type n) const;
+
protected:
static const unsigned char sm_alphabet[];
diff --git a/vmime/utility/encoder/defaultEncoder.hpp b/vmime/utility/encoder/defaultEncoder.hpp
index 6a11a262..3f7d9111 100644
--- a/vmime/utility/encoder/defaultEncoder.hpp
+++ b/vmime/utility/encoder/defaultEncoder.hpp
@@ -44,6 +44,9 @@ public:
utility::stream::size_type encode(utility::inputStream& in, utility::outputStream& out, utility::progressListener* progress = NULL);
utility::stream::size_type decode(utility::inputStream& in, utility::outputStream& out, utility::progressListener* progress = NULL);
+
+ utility::stream::size_type getEncodedSize(const utility::stream::size_type n) const;
+ utility::stream::size_type getDecodedSize(const utility::stream::size_type n) const;
};
diff --git a/vmime/utility/encoder/encoder.hpp b/vmime/utility/encoder/encoder.hpp
index 9b6bbbe0..65114867 100644
--- a/vmime/utility/encoder/encoder.hpp
+++ b/vmime/utility/encoder/encoder.hpp
@@ -91,6 +91,24 @@ public:
*/
const propertySet& getResults() const;
+ /** Return the encoded size for the specified input (decoded) size.
+ * If the size is not exact, it may be an estimate which should always
+ * be larger than the actual encoded size.
+ *
+ * @param n count of input (decoded) bytes
+ * @return count of output (encoded) bytes
+ */
+ virtual utility::stream::size_type getEncodedSize(const utility::stream::size_type n) const = 0;
+
+ /** Return the encoded size for the specified input (encoded) size.
+ * If the size is not exact, it may be an estimate which should always
+ * be larger than the actual decoded size.
+ *
+ * @param n count of input (encoded) bytes
+ * @return count of output (decoded) bytes
+ */
+ virtual utility::stream::size_type getDecodedSize(const utility::stream::size_type n) const = 0;
+
protected:
propertySet& getResults();
diff --git a/vmime/utility/encoder/qpEncoder.hpp b/vmime/utility/encoder/qpEncoder.hpp
index d4dcc1de..254a38d0 100644
--- a/vmime/utility/encoder/qpEncoder.hpp
+++ b/vmime/utility/encoder/qpEncoder.hpp
@@ -50,6 +50,9 @@ public:
static bool RFC2047_isEncodingNeededForChar(const unsigned char c);
static int RFC2047_getEncodedLength(const unsigned char c);
+ utility::stream::size_type getEncodedSize(const utility::stream::size_type n) const;
+ utility::stream::size_type getDecodedSize(const utility::stream::size_type n) const;
+
protected:
static const unsigned char sm_hexDigits[17];
diff --git a/vmime/utility/encoder/uuEncoder.hpp b/vmime/utility/encoder/uuEncoder.hpp
index 09eb667d..f0bcbb33 100644
--- a/vmime/utility/encoder/uuEncoder.hpp
+++ b/vmime/utility/encoder/uuEncoder.hpp
@@ -46,6 +46,9 @@ public:
utility::stream::size_type decode(utility::inputStream& in, utility::outputStream& out, utility::progressListener* progress = NULL);
const std::vector <string> getAvailableProperties() const;
+
+ utility::stream::size_type getEncodedSize(const utility::stream::size_type n) const;
+ utility::stream::size_type getDecodedSize(const utility::stream::size_type n) const;
};