package com.fsck.k9.mail.filter;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* Further encode a quoted-printable stream into a safer format for signed email.
*
* @see <a href="http://tools.ietf.org/html/rfc2015">RFC-2015</a>
*/
public class SignSafeOutputStream extends FilterOutputStream {
private static final byte[] ESCAPED_SPACE = new byte[] { '=', '2', '0' };
private static final int DEFAULT_BUFFER_SIZE = 1024;
private State state = State.cr_FROM;
private final byte[] outBuffer;
private int outputIndex;
private boolean closed = false;
public SignSafeOutputStream(OutputStream out) {
super(out);
outBuffer = new byte[DEFAULT_BUFFER_SIZE];
}
public void encode(byte next) throws IOException {
State nextState = state.nextState(next);
if (nextState == State.SPACE_FROM) {
state = State.INIT;
writeToBuffer(ESCAPED_SPACE[0]);
writeToBuffer(ESCAPED_SPACE[1]);
writeToBuffer(ESCAPED_SPACE[2]);
} else {
state = nextState;
writeToBuffer(next);
}
}
private void writeToBuffer(byte next) throws IOException {
outBuffer[outputIndex++] = next;
if (outputIndex >= outBuffer.length) {
flushOutput();
}
}
void flushOutput() throws IOException {
if (outputIndex < outBuffer.length) {
out.write(outBuffer, 0, outputIndex);
} else {
out.write(outBuffer);
}
outputIndex = 0;
}
@Override
public void write(int b) throws IOException {
if (closed) {
throw new IOException("Stream has been closed");
}
encode((byte) b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
if (closed) {
throw new IOException("Stream has been closed");
}
for (int inputIndex = off; inputIndex < len + off; inputIndex++) {
encode(b[inputIndex]);
}
}
@Override
public void flush() throws IOException {
flushOutput();
out.flush();
}
@Override
public void close() throws IOException {
if (closed) {
return;
}
try {
flush();
} finally {
closed = true;
}
}
enum State {
INIT {
@Override
public State nextState(int b) {
switch (b) {
case '\r':
return lf_FROM;
default:
return INIT;
}
}
},
lf_FROM {
@Override
public State nextState(int b) {
switch (b) {
case '\n':
return cr_FROM;
case '\r':
return lf_FROM;
default:
return INIT;
}
}
},
cr_FROM {
@Override
public State nextState(int b) {
switch (b) {
case 'F':
return F_FROM;
case '\r':
return lf_FROM;
default:
return INIT;
}
}
},
F_FROM {
@Override
public State nextState(int b) {
switch (b) {
case 'r':
return R_FROM;
case '\r':
return lf_FROM;
default:
return INIT;
}
}
},
R_FROM {
@Override
public State nextState(int b) {
switch (b) {
case 'o':
return O_FROM;
case '\r':
return lf_FROM;
default:
return INIT;
}
}
},
O_FROM {
@Override
public State nextState(int b) {
switch (b) {
case 'm':
return M_FROM;
case '\r':
return lf_FROM;
default:
return INIT;
}
}
},
M_FROM {
@Override
public State nextState(int b) {
switch (b) {
case ' ':
return SPACE_FROM;
case '\r':
return lf_FROM;
default:
return INIT;
}
}
},
SPACE_FROM {
@Override
public State nextState(int b) {
switch (b) {
case '\r':
return lf_FROM;
default:
return INIT;
}
}
};
public abstract State nextState(int b);
}
}