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 text prologText = getActualPrologText(ctx); + const text epilogText = getActualEpilogText(ctx); - const string& epilogText = - m_epilogText.empty() - ? (isRootPart() - ? ctx.getEpilogText() - : NULL_STRING - ) - : m_epilogText; - - if (!prologText.empty()) + if (!prologText.isEmpty()) { - text prolog(prologText, vmime::charset("us-ascii")); - - prolog.encodeAndFold(ctx, os, 0, + 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 srcEncoder = m_contents->getEncoding().getEncoder(); + ref 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 bodyPart::clone() const { ref p = vmime::create (); 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 > children = getChildComponents(); + utility::stream::size_type totalSize = 0; + + for (std::vector >::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 header::clone() const { ref
hdr = vmime::create
(); 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 +// +// 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 @@ -88,6 +88,13 @@ ref SMTPCommand::STARTTLS() // static ref SMTPCommand::MAIL(const mailbox& mbox, const bool utf8) +{ + return MAIL(mbox, utf8, 0); +} + + +// static +ref SMTPCommand::MAIL(const mailbox& mbox, const bool utf8, const unsigned long size) { std::ostringstream cmd; cmd.imbue(std::locale::classic()); @@ -108,6 +115,9 @@ ref 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 ("maxlinelength", static_cast (-1)); + + const bool cutLines = (propMaxLineLength != static_cast (-1)); + const string::size_type maxLineLength = std::min(propMaxLineLength, static_cast (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 ("maxlinelength", static_cast (-1)); + + const bool cutLines = (propMaxLineLength != static_cast (-1)); + const string::size_type maxLineLength = std::min(propMaxLineLength, static_cast (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: SMTPUTF8", cmd->getText()); } + void testMAIL_SIZE() + { + vmime::ref cmd = SMTPCommand::MAIL + (vmime::mailbox("me@vmime.org"), false, 123456789); + + VASSERT_NOT_NULL("Not null", cmd); + VASSERT_EQ("Text", "MAIL FROM: SIZE=123456789", cmd->getText()); + } + + void testMAIL_SIZE_UTF8() + { + vmime::ref cmd = SMTPCommand::MAIL + (vmime::mailbox(vmime::emailAddress("mailtest", "例え.テスト")), true, 123456789); + + VASSERT_NOT_NULL("Not null", cmd); + VASSERT_EQ("Text", "MAIL FROM: SMTPUTF8 SIZE=123456789", cmd->getText()); + } + void testRCPT() { vmime::ref cmd = SMTPCommand::RCPT(vmime::mailbox("someone@vmime.org"), 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); } + void testSize_Chunking() + { + vmime::ref session = + vmime::create (); + + vmime::ref tr = session->getTransport + (vmime::utility::url("smtp://localhost")); + + tr->setSocketFactory(vmime::create > >()); + tr->setTimeoutHandlerFactory(vmime::create ()); + + tr->connect(); + + VASSERT("Test server should report it supports the SIZE extension!", + tr.dynamicCast ()->getConnection()->hasExtension("SIZE")); + + vmime::mailbox exp("expeditor@test.vmime.org"); + + vmime::mailboxList recips; + recips.appendMailbox(vmime::create ("recipient@test.vmime.org")); + + vmime::ref msg = vmime::create (); + + VASSERT_THROW("Connection", tr->send(msg, exp, recips), + vmime::exceptions::message_size_exceeds_max_limits); + } + + void testSize_NoChunking() + { + vmime::ref session = + vmime::create (); + + vmime::ref tr = session->getTransport + (vmime::utility::url("smtp://localhost")); + + tr->setSocketFactory(vmime::create > >()); + tr->setTimeoutHandlerFactory(vmime::create ()); + + tr->connect(); + + VASSERT("Test server should report it supports the SIZE extension!", + tr.dynamicCast ()->getConnection()->hasExtension("SIZE")); + + vmime::mailbox exp("expeditor@test.vmime.org"); + + vmime::mailboxList recips; + recips.appendMailbox(vmime::create ("recipient@test.vmime.org")); + + vmime::ref msg = vmime::create (); + + VASSERT_THROW("Connection", tr->send(msg, exp, recips), + vmime::exceptions::message_size_exceeds_max_limits); + } + 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() - { - m_recipients.insert("recipient1@test.vmime.org"); - m_recipients.insert("recipient2@test.vmime.org"); - m_recipients.insert("recipient3@test.vmime.org"); - - 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:"), 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 ::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 .\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 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 & getChunks() const - { - static std::vector chunks; - - if (chunks.size() == 0) - { - chunks.push_back(vmime::string(1000, 'A')); - chunks.push_back(vmime::string(3000, 'B')); - chunks.push_back(vmime::string(500000, 'C')); - chunks.push_back(vmime::string(25000, 'D')); - } - - return chunks; - } - - void generateImpl(const vmime::generationContext& /* ctx */, - vmime::utility::outputStream& outputStream, - const vmime::string::size_type /* curLinePos */ = 0, - vmime::string::size_type* /* newLinePos */ = NULL) const - { - for (unsigned int i = 0, n = getChunks().size() ; i < n ; ++i) - { - const vmime::string& chunk = getChunks()[i]; - outputStream.write(chunk.data(), chunk.size()); - } - } -}; - - diff --git a/tests/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 +// +// 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("recipient1@test.vmime.org"); + m_recipients.insert("recipient2@test.vmime.org"); + m_recipients.insert("recipient3@test.vmime.org"); + + 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:"), 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 ::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 .\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 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 & getChunks() const + { + static std::vector 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 +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:", 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 +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 +// +// 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 msg = vmime::create (); + msg->getHeader()->getField("Foo")->setValue(vmime::string("bar")); + + vmime::htmlTextPart textPart; + textPart.setPlainText(vmime::create ("Foo bar bazé foo foo foo")); + textPart.setText(vmime::create ("Foo bar bazé 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 getEncoder(const vmime::string& name, + int maxLineLength = 0, const vmime::propertySet props = vmime::propertySet()) { vmime::ref 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 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 enc = - vmime::utility::encoder::encoderFactory::getInstance()->create(name); - - if (maxLineLength != 0) - enc->getProperties()["maxlinelength"] = maxLineLength; + vmime::ref 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 > 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 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 > getChildComponents(); + utility::stream::size_type getGeneratedSize(const generationContext& ctx); + private: ref
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 > 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 > getChildComponents(); + utility::stream::size_type getGeneratedSize(const generationContext& ctx); + private: std::vector > 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 AUTH(const string& mechName); static ref STARTTLS(); static ref MAIL(const mailbox& mbox, const bool utf8); + static ref MAIL(const mailbox& mbox, const bool utf8, const unsigned long size); static ref RCPT(const mailbox& mbox, const bool utf8); static ref RSET(); static ref 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 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 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 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; };