Added dotFilteredInputStream + fixed CRLFToLFFilteredOutputStream + added unit tests.

This commit is contained in:
Vincent Richard 2005-06-15 22:22:01 +00:00
parent ecae17af35
commit 6bf5f9192e
4 changed files with 320 additions and 3 deletions

View File

@ -342,6 +342,7 @@ libvmimetest_sources = [
[ 'tests/parser/pathTest', [ 'tests/parser/pathTest.cpp' ] ],
[ 'tests/parser/parameterTest', [ 'tests/parser/parameterTest.cpp' ] ],
[ 'tests/parser/textTest', [ 'tests/parser/textTest.cpp' ] ],
[ 'tests/utility/filteredStreamTest', [ 'tests/utility/filteredStreamTest.cpp' ] ],
[ 'tests/utility/md5Test', [ 'tests/utility/md5Test.cpp' ] ],
[ 'tests/utility/stringProxyTest', [ 'tests/utility/stringProxyTest.cpp' ] ],
[ 'tests/utility/stringUtilsTest', [ 'tests/utility/stringUtilsTest.cpp' ] ],

View File

@ -21,10 +21,99 @@
#include <algorithm>
namespace vmime {
namespace utility {
// dotFilteredInputStream
dotFilteredInputStream::dotFilteredInputStream(inputStream& is)
: m_stream(is)
{
}
inputStream& dotFilteredInputStream::getPreviousInputStream()
{
return (m_stream);
}
const bool dotFilteredInputStream::eof() const
{
return (m_stream.eof());
}
void dotFilteredInputStream::reset()
{
m_previousChar2 = '\0';
m_previousChar1 = '\0';
m_stream.reset();
}
const stream::size_type dotFilteredInputStream::read(value_type* const data, const size_type count)
{
const stream::size_type read = m_stream.read(data, count);
const value_type* readPtr = data;
value_type* writePtr = data;
const value_type* end = data + read;
stream::size_type written = 0;
// Replace "\n.." with "\n."
while (readPtr < end)
{
if (*readPtr == '.')
{
const value_type prevChar2 =
(readPtr == data + 1 ? m_previousChar1 :
readPtr == data ? m_previousChar2 : *(readPtr - 2));
const value_type prevChar1 =
(readPtr == data ? m_previousChar1 : *(readPtr - 1));
if (prevChar2 == '\n' && prevChar1 == '.')
{
// Ignore last dot
}
else
{
*writePtr = *readPtr;
++writePtr;
++written;
}
}
else
{
*writePtr = *readPtr;
++writePtr;
++written;
}
++readPtr;
}
m_previousChar2 = (read >= 2 ? data[read - 2] : m_previousChar1);
m_previousChar1 = (read >= 1 ? data[read - 1] : '\0');
return (written);
}
const stream::size_type dotFilteredInputStream::skip(const size_type /* count */)
{
// Skipping bytes is not supported
return 0;
}
// dotFilteredOutputStream
dotFilteredOutputStream::dotFilteredOutputStream(outputStream& os)
@ -42,6 +131,9 @@ outputStream& dotFilteredOutputStream::getNextOutputStream()
void dotFilteredOutputStream::write
(const value_type* const data, const size_type count)
{
if (count == 0)
return;
const value_type* pos = data;
const value_type* end = data + count;
const value_type* start = data;
@ -85,10 +177,25 @@ outputStream& CRLFToLFFilteredOutputStream::getNextOutputStream()
void CRLFToLFFilteredOutputStream::write
(const value_type* const data, const size_type count)
{
if (count == 0)
return;
const value_type* pos = data;
const value_type* end = data + count;
const value_type* start = data;
// Warning: if the whole buffer finishes with '\r', this
// last character will not be written back...
// TODO: add a finalize() method?
if (m_previousChar == '\r')
{
if (*pos != '\n')
{
m_stream.write("\r", 1); // write back \r
m_previousChar = *pos;
}
}
// Replace "\r\n" (CRLF) with "\n" (LF)
while ((pos = std::find(pos, end, '\n')) != end)
{
@ -97,7 +204,9 @@ void CRLFToLFFilteredOutputStream::write
if (previousChar == '\r')
{
m_stream.write(start, pos - 1 - data); // do not write \r
if (pos != data)
m_stream.write(start, pos - 1 - data); // do not write \r
m_stream.write("\n", 1);
start = pos + 1;
@ -106,8 +215,16 @@ void CRLFToLFFilteredOutputStream::write
++pos;
}
m_stream.write(start, end - start);
m_previousChar = data[count - 1];
if (data[count - 1] == '\r')
{
m_stream.write(start, end - start - 1);
m_previousChar = '\r';
}
else
{
m_stream.write(start, end - start);
m_previousChar = data[count - 1];
}
}

View File

@ -0,0 +1,166 @@
//
// VMime library (http://www.vmime.org)
// Copyright (C) 2002-2005 Vincent Richard <vincent@vincent-richard.net>
//
// 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA.
//
#include "../lib/unit++/unit++.h"
#include <iostream>
#include <ostream>
#include <algorithm>
#include "vmime/vmime.hpp"
#include "vmime/platforms/posix/posixHandler.hpp"
#include "vmime/utility/filteredStream.hpp"
using namespace unitpp;
namespace
{
class filteredStreamTest : public suite
{
class chunkInputStream : public vmime::utility::inputStream
{
private:
std::vector <std::string> m_chunks;
std::vector <std::string>::size_type m_index;
public:
chunkInputStream() : m_index(0) { }
void addChunk(const std::string& chunk) { m_chunks.push_back(chunk); }
const bool eof() const { return (m_index >= m_chunks.size()); }
void reset() { m_index = 0; }
const size_type read(value_type* const data, const size_type /* count */)
{
const std::string chunk = m_chunks[m_index];
// Warning: 'count' should be larger than chunk length.
// This is OK for our tests.
std::copy(chunk.begin(), chunk.end(), data);
++m_index;
return chunk.length();
}
const size_type skip(const size_type /* count */)
{
// Not supported
return 0;
}
};
void testDotFilteredInputStreamHelper
(const std::string& number, const std::string& expected,
const std::string& c1, const std::string& c2 = "",
const std::string& c3 = "", const std::string& c4 = "")
{
chunkInputStream cis;
cis.addChunk(c1);
if (!c2.empty()) cis.addChunk(c2);
if (!c3.empty()) cis.addChunk(c3);
if (!c4.empty()) cis.addChunk(c4);
vmime::utility::dotFilteredInputStream is(cis);
std::ostringstream oss;
vmime::utility::outputStreamAdapter os(oss);
vmime::utility::bufferedStreamCopy(is, os);
assert_eq(number, expected, oss.str());
}
void testDotFilteredInputStream()
{
testDotFilteredInputStreamHelper("1", "foo\n.bar", "foo\n..bar");
testDotFilteredInputStreamHelper("2", "foo\n.bar", "foo\n", "..bar");
testDotFilteredInputStreamHelper("3", "foo\n.bar", "foo\n.", ".bar");
testDotFilteredInputStreamHelper("4", "foo\n.bar", "foo\n..", "bar");
testDotFilteredInputStreamHelper("5", "foo\n.bar", "foo\n", ".", ".bar");
testDotFilteredInputStreamHelper("6", "foo\n.bar", "foo\n", ".", ".", "bar");
}
template <typename FILTER>
void testFilteredOutputStreamHelper
(const std::string& number, const std::string& expected,
const std::string& c1, const std::string& c2 = "",
const std::string& c3 = "", const std::string& c4 = "")
{
std::ostringstream oss;
vmime::utility::outputStreamAdapter os(oss);
FILTER fos(os);
fos.write(c1.data(), c1.length());
if (!c2.empty()) fos.write(c2.data(), c2.length());
if (!c3.empty()) fos.write(c3.data(), c3.length());
if (!c4.empty()) fos.write(c4.data(), c4.length());
assert_eq(number, expected, oss.str());
}
void testDotFilteredOutputStream()
{
typedef vmime::utility::dotFilteredOutputStream FILTER;
testFilteredOutputStreamHelper<FILTER>("1", "foo\n..bar", "foo\n.bar");
testFilteredOutputStreamHelper<FILTER>("2", "foo\n..bar", "foo\n", ".bar");
testFilteredOutputStreamHelper<FILTER>("3", "foo\n..bar", "foo", "\n.bar");
testFilteredOutputStreamHelper<FILTER>("4", "foo\n..bar", "foo", "\n", ".bar");
testFilteredOutputStreamHelper<FILTER>("5", "foo\n..bar", "foo", "\n", ".", "bar");
}
void testCRLFToLFFilteredOutputStream()
{
typedef vmime::utility::CRLFToLFFilteredOutputStream FILTER;
testFilteredOutputStreamHelper<FILTER>("1", "foo\nbar", "foo\r\nbar");
testFilteredOutputStreamHelper<FILTER>("2", "foo\nbar", "foo\r\n", "bar");
testFilteredOutputStreamHelper<FILTER>("3", "foo\nbar", "foo\r", "\nbar");
testFilteredOutputStreamHelper<FILTER>("4", "foo\nbar", "foo", "\r\nbar");
testFilteredOutputStreamHelper<FILTER>("5", "foo\nbar", "foo", "\r", "\nbar");
testFilteredOutputStreamHelper<FILTER>("6", "foo\nbar", "foo", "\r", "\n", "bar");
}
public:
filteredStreamTest() : suite("vmime::utility::filteredStream")
{
// VMime initialization
vmime::platformDependant::setHandler<vmime::platforms::posix::posixHandler>();
add("dotFilteredInputStream", testcase(this, "dotFilteredInputStream", &filteredStreamTest::testDotFilteredInputStream));
add("dotFilteredOutputStream", testcase(this, "dotFilteredOutputStream", &filteredStreamTest::testDotFilteredOutputStream));
add("CRLFToLFFilteredOutputStream", testcase(this, "CRLFToLFFilteredOutputStream", &filteredStreamTest::testCRLFToLFFilteredOutputStream));
suite::main().add("vmime::utility::filteredStream", this);
}
};
filteredStreamTest* theTest = new filteredStreamTest();
}

View File

@ -58,6 +58,39 @@ public:
};
/** A filtered input stream which replaces "\n.."
* sequences with "\n." sequences.
*/
class dotFilteredInputStream : public filteredInputStream
{
public:
/** Construct a new filter for the specified input stream.
*
* @param is stream from which to read data to be filtered
*/
dotFilteredInputStream(inputStream& is);
inputStream& getPreviousInputStream();
const bool eof() const;
void reset();
const size_type read(value_type* const data, const size_type count);
const size_type skip(const size_type count);
private:
inputStream& m_stream;
value_type m_previousChar2; // (N - 1)th character of previous buffer
value_type m_previousChar1; // (N)th (last) character of previous buffer
};
/** A filtered output stream which replaces "\n."
* sequences with "\n.." sequences.
*/