package org.subethamail.smtp.io;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
/**
* An InputStream class that terminates the stream when it encounters a US-ASCII
* encoded dot CR LF byte sequence immediately following a CR LF line end.
*/
public class DotTerminatedInputStream extends InputStream
{
/**
* The wrapped input stream.
*/
private InputStream in;
/**
* The last bytes returned by the {@link #read()} function. The first byte
* in the array contains the byte returned by the penultimate read() call.
* The second byte in the array contains the byte returned by the last
* read() call. EOF (-1) is not shifted into the array. It's initial value
* is CR LF, so the first character of the stream is considered to be the
* first character of a line. This makes it possible to receive empty data.
*/
private final byte[] lastBytes = new byte[]{
'\r', '\n'
};
/**
* The buffer which contains the bytes read from the underlying stream in
* advance. These bytes are not yet returned by the {@link #read()}
* function. Null means uninitialized.
*/
private int[] nextBytes = null;
/**
* Indicates that the last byte - not including the terminating sequence -
* of the wrapped stream was already returned by {@link #read()}
*/
private boolean endReached = false;
/**
* A constructor for this object that takes a stream to be wrapped and a
* terminating character sequence.
*
* @param in
* the <code>InputStream</code> to be wrapped
* @throws IllegalArgumentException
* if the terminator array is null or empty
*/
public DotTerminatedInputStream(InputStream in)
{
this.in = in;
}
@Override
public int read() throws IOException
{
if (nextBytes == null)
initNextBytes();
if (endReached)
return -1;
if (lastBytesAreCrLf() && nextBytesAreDotCrLf())
{
endReached = true;
return -1;
}
int result = nextBytes[0];
if (result == -1)
{
// End of stream reached without seeing the terminator
throw new EOFException("Pre-mature end of <CRLF>.<CRLF> terminated data");
}
readWrappedStream();
return result;
}
private void initNextBytes() throws IOException
{
nextBytes = new int[3];
nextBytes[0] = in.read();
nextBytes[1] = in.read();
nextBytes[2] = in.read();
}
private boolean lastBytesAreCrLf()
{
return lastBytes[0] == '\r' && lastBytes[1] == '\n';
}
private boolean nextBytesAreDotCrLf()
{
return nextBytes[0] == '.' && nextBytes[1] == '\r' && nextBytes[2] == '\n';
}
/**
* Shifts bytes in the buffers, reads a byte from the wrapped stream, and
* places it at the end of the nextBytes buffer.
*/
private void readWrappedStream() throws IOException
{
lastBytes[0] = lastBytes[1];
// casting is safe, this function is not called if an - unexpected - EOF
// was read
lastBytes[1] = (byte) nextBytes[0];
nextBytes[0] = nextBytes[1];
nextBytes[1] = nextBytes[2];
nextBytes[2] = in.read();
}
}