diff --git a/src/utility/filteredStream.cpp b/src/utility/filteredStream.cpp index 9af7b32a..294a7a94 100644 --- a/src/utility/filteredStream.cpp +++ b/src/utility/filteredStream.cpp @@ -286,6 +286,77 @@ void CRLFToLFFilteredOutputStream::flush() } +// LFToCRLFFilteredOutputStream + +LFToCRLFFilteredOutputStream::LFToCRLFFilteredOutputStream(outputStream& os) + : m_stream(os), m_previousChar('\0') +{ +} + + +outputStream& LFToCRLFFilteredOutputStream::getNextOutputStream() +{ + return (m_stream); +} + + +void LFToCRLFFilteredOutputStream::write + (const value_type* const data, const size_type count) +{ + if (count == 0) + return; + + std::string buffer; + buffer.reserve(count); + + const value_type* pos = data; + const value_type* end = data + count; + + value_type previousChar = m_previousChar; + + while (pos < end) + { + switch (*pos) + { + case '\r': + + buffer.append(1, '\r'); + buffer.append(1, '\n'); + + break; + + case '\n': + + if (previousChar != '\r') + { + buffer.append(1, '\r'); + buffer.append(1, '\n'); + } + + break; + + default: + + buffer.append(1, *pos); + break; + } + + previousChar = *pos; + ++pos; + } + + m_stream.write(&buffer[0], buffer.length()); + + m_previousChar = previousChar; +} + + +void LFToCRLFFilteredOutputStream::flush() +{ + m_stream.flush(); +} + + // stopSequenceFilteredInputStream <1> template <> diff --git a/tests/testUtils.hpp b/tests/testUtils.hpp index 4fa6b457..1e138301 100644 --- a/tests/testUtils.hpp +++ b/tests/testUtils.hpp @@ -53,6 +53,8 @@ #define VASSERT_EQ(msg, expected, actual) \ CPPUNIT_ASSERT_EQUAL_MESSAGE(std::string(msg), expected, actual) +#define VASSERT_NEQ(msg, expected, actual) \ + CPPUNIT_ASSERT_MESSAGE(std::string(msg), (expected) != (actual)) #define VASSERT_THROW(msg, expression, exceptionType) \ CPPUNIT_ASSERT_THROW(expression, exceptionType) diff --git a/tests/utility/filteredStreamTest.cpp b/tests/utility/filteredStreamTest.cpp index 4a869c6e..76cb149b 100644 --- a/tests/utility/filteredStreamTest.cpp +++ b/tests/utility/filteredStreamTest.cpp @@ -35,6 +35,8 @@ VMIME_TEST_SUITE_BEGIN(filteredStreamTest) VMIME_TEST(testStopSequenceFilteredInputStream1) VMIME_TEST(testStopSequenceFilteredInputStreamN_2) VMIME_TEST(testStopSequenceFilteredInputStreamN_3) + VMIME_TEST(testLFToCRLFFilteredOutputStream_Global) + VMIME_TEST(testLFToCRLFFilteredOutputStream_Edge) VMIME_TEST_LIST_END @@ -276,5 +278,43 @@ VMIME_TEST_SUITE_BEGIN(filteredStreamTest) testStopSequenceFISHelper <3>("24", "xyz", "", "x", "y", "z"); } + + // LFToCRLFFilteredOutputStream + + void testLFToCRLFFilteredOutputStream_Global() + { + typedef vmime::utility::LFToCRLFFilteredOutputStream FILTER; + + testFilteredOutputStreamHelper("1", "ABC\r\nDEF", "ABC\nDEF"); + testFilteredOutputStreamHelper("2", "ABC\r\nDEF", "ABC\rDEF"); + testFilteredOutputStreamHelper("3", "\r\n\r\nAB\r\n\r\nA\r\nB\r\n", "\n\nAB\n\nA\nB\n"); + testFilteredOutputStreamHelper("4", "ABCDE\r\nF", "ABCDE\nF"); + testFilteredOutputStreamHelper("5", "ABCDE\r\nF", "ABCDE\r\nF"); + testFilteredOutputStreamHelper("6", "\r\n\r\n\r\n", "\n\n\n"); + testFilteredOutputStreamHelper("7", "\r\n\r\n\r\n", "\r\r\n\n"); + testFilteredOutputStreamHelper("8", "\r\n\r\n\r\n\r\n", "\r\r\r\r"); + testFilteredOutputStreamHelper("9", "\r\n\r\n\r\n\r\n", "\n\n\n\n"); + testFilteredOutputStreamHelper("10", "\r\n\r\n\r\n", "\r\n\n\n"); + testFilteredOutputStreamHelper("11", "\r\n\r\n\r\n\r\n", "\n\n\n\r\n"); + } + + void testLFToCRLFFilteredOutputStream_Edge() + { + typedef vmime::utility::LFToCRLFFilteredOutputStream FILTER; + + testFilteredOutputStreamHelper("1", "\r\n\r\n", "\r", "\r"); + testFilteredOutputStreamHelper("2", "\r\n\r\n", "\r", "\n\r"); + testFilteredOutputStreamHelper("3", "ABC\r\n\r\n", "ABC\r", "\n\r"); + testFilteredOutputStreamHelper("4", "ABC\r\n\r\n\r\n", "ABC\r", "\n\r", "\n\n"); + testFilteredOutputStreamHelper("5", "\r\n\r\n", "\n", "\n"); + testFilteredOutputStreamHelper("6", "\r\n\r\n", "\r\n\r\n"); + testFilteredOutputStreamHelper("7", "\r\n\r\n", "\r\n\r", "\n"); + + testFilteredOutputStreamHelper("8", "A\r\nB\r\nC\r\nD", "A\rB", "\nC\r\nD"); + testFilteredOutputStreamHelper("9", "\r\nA\r\nB\r\nC\r\nD", "\rA\r", "B\nC\r\nD"); + testFilteredOutputStreamHelper("10", "\r\nA\r\nB\r\nC\r\nD", "\nA\r", "B\nC\r\nD"); + testFilteredOutputStreamHelper("11", "\r\nA\r\nB\r\nC\r\nD\r\n", "\nA\rB", "\nC\r\nD\r"); + } + VMIME_TEST_SUITE_END diff --git a/vmime/utility/filteredStream.hpp b/vmime/utility/filteredStream.hpp index b7ec010f..9dc24e5d 100644 --- a/vmime/utility/filteredStream.hpp +++ b/vmime/utility/filteredStream.hpp @@ -155,6 +155,32 @@ private: }; +/** A filtered output stream which replaces CR or LF characters + * with CRLF sequences. + */ + +class LFToCRLFFilteredOutputStream : public filteredOutputStream +{ +public: + + /** Construct a new filter for the specified output stream. + * + * @param os stream into which write filtered data + */ + LFToCRLFFilteredOutputStream(outputStream& os); + + outputStream& getNextOutputStream(); + + void write(const value_type* const data, const size_type count); + void flush(); + +private: + + outputStream& m_stream; + value_type m_previousChar; +}; + + /** A filtered input stream which stops when a specified sequence * is found (eof() method will return 'true'). */