This commit is contained in:
parent
66520a2cb3
commit
90c6b666c8
3 changed files with 266 additions and 0 deletions
|
@ -0,0 +1,36 @@
|
|||
package com.fsck.k9.mail.filter;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Created by alexandre on 11/10/15.
|
||||
*/
|
||||
public class SignSafeOutputStreamTest {
|
||||
private static final String INPUT_STRING = "It's generally a good idea to encode lines that begin with\r\n"
|
||||
+ "From because some mail transport agents will insert a greater-\r\n"
|
||||
+ "than (>) sign, thus invalidating the signature.\r\n\r\n"
|
||||
+ "Also, in some cases it might be desirable to encode any \r\n"
|
||||
+ "trailing whitespace that occurs on lines in order to ensure \r\n"
|
||||
+ "that the message signature is not invalidated when passing \r\n"
|
||||
+ "a gateway that modifies such whitespace (like BITNET). \r\n\r\n";
|
||||
|
||||
private static final String EXPECTED = "It's generally a good idea to encode lines that begin with\r\n"
|
||||
+ "From=20because some mail transport agents will insert a greater-\r\n"
|
||||
+ "than (>) sign, thus invalidating the signature.\r\n\r\n"
|
||||
+ "Also, in some cases it might be desirable to encode any =20\r\n"
|
||||
+ "trailing whitespace that occurs on lines in order to ensure =20\r\n"
|
||||
+ "that the message signature is not invalidated when passing =20\r\n"
|
||||
+ "a gateway that modifies such whitespace (like BITNET). =20\r\n\r\n";
|
||||
|
||||
@Test
|
||||
public void testWrite() throws IOException {
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
new SignSafeOutputStream(output).write(INPUT_STRING.getBytes("US-ASCII"));
|
||||
assertEquals(EXPECTED, new String(output.toByteArray(), "US-ASCII"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
package com.fsck.k9.mail.filter;
|
||||
|
||||
import org.apache.james.mime4j.codec.QuotedPrintableOutputStream;
|
||||
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* 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 ByteBuffer buffer = ByteBuffer.allocate(1);
|
||||
|
||||
private State state = State.cr_FROM;
|
||||
|
||||
public SignSafeOutputStream(OutputStream out){
|
||||
super(out);
|
||||
}
|
||||
|
||||
public static FilterOutputStream newInstance(OutputStream out){
|
||||
return new QuotedPrintableOutputStream(new SignSafeOutputStream(out), false);
|
||||
}
|
||||
|
||||
private static final byte[] ESCAPE = new byte[]{'=','2','0'};
|
||||
|
||||
@Override
|
||||
public void write(int oneByte) throws IOException {
|
||||
State nextState = state.nextState(oneByte);
|
||||
if (nextState == State.SPACE_FROM){
|
||||
state = State.INIT;
|
||||
out.write(ESCAPE);
|
||||
} else if (nextState == State.lf_SPACE){
|
||||
buffer.clear();
|
||||
state = State.lf_FROM;
|
||||
out.write(ESCAPE);
|
||||
out.write(oneByte);
|
||||
} else if (nextState == State.SPACE) {
|
||||
writeBuffer();
|
||||
buffer.put((byte)32);
|
||||
state = nextState;
|
||||
} else {
|
||||
writeBuffer();
|
||||
state = nextState;
|
||||
out.write(oneByte);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeBuffer() throws IOException {
|
||||
buffer.flip();
|
||||
if (buffer.hasRemaining()) {
|
||||
out.write(buffer.get());
|
||||
}
|
||||
buffer.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
writeBuffer();
|
||||
out.close();
|
||||
}
|
||||
|
||||
enum State {
|
||||
INIT {
|
||||
@Override
|
||||
public State nextState(int b) {
|
||||
switch (b) {
|
||||
case '\r':
|
||||
return lf_FROM;
|
||||
case ' ':
|
||||
return SPACE;
|
||||
default:
|
||||
return INIT;
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
lf_FROM {
|
||||
@Override
|
||||
public State nextState(int b) {
|
||||
switch (b) {
|
||||
case '\n':
|
||||
return cr_FROM;
|
||||
case ' ':
|
||||
return SPACE;
|
||||
case '\r':
|
||||
return lf_FROM;
|
||||
default:
|
||||
return INIT;
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
cr_FROM {
|
||||
@Override
|
||||
public State nextState(int b) {
|
||||
switch (b) {
|
||||
case 'F':
|
||||
return F_FROM;
|
||||
case ' ':
|
||||
return SPACE;
|
||||
case '\r':
|
||||
return lf_FROM;
|
||||
default:
|
||||
return INIT;
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
F_FROM {
|
||||
@Override
|
||||
public State nextState(int b) {
|
||||
switch (b) {
|
||||
case 'r':
|
||||
return R_FROM;
|
||||
case ' ':
|
||||
return SPACE;
|
||||
case '\r':
|
||||
return lf_FROM;
|
||||
default:
|
||||
return INIT;
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
R_FROM {
|
||||
@Override
|
||||
public State nextState(int b) {
|
||||
switch (b) {
|
||||
case 'o':
|
||||
return O_FROM;
|
||||
case ' ':
|
||||
return SPACE;
|
||||
case '\r':
|
||||
return lf_FROM;
|
||||
default:
|
||||
return INIT;
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
O_FROM {
|
||||
@Override
|
||||
public State nextState(int b) {
|
||||
switch (b) {
|
||||
case 'm':
|
||||
return M_FROM;
|
||||
case ' ':
|
||||
return SPACE;
|
||||
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_SPACE;
|
||||
case 'F':
|
||||
return F_FROM;
|
||||
case ' ':
|
||||
return SPACE;
|
||||
default:
|
||||
return INIT;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
SPACE {
|
||||
@Override
|
||||
public State nextState(int b) {
|
||||
switch (b) {
|
||||
case '\r':
|
||||
return lf_SPACE;
|
||||
case 'F':
|
||||
return F_FROM;
|
||||
case ' ':
|
||||
return SPACE;
|
||||
default:
|
||||
return INIT;
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
lf_SPACE {
|
||||
@Override
|
||||
public State nextState(int b) {
|
||||
return INIT;
|
||||
}
|
||||
};
|
||||
|
||||
public abstract State nextState(int b);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -11,6 +11,8 @@ import java.io.OutputStream;
|
|||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import com.fsck.k9.mail.filter.CountingOutputStream;
|
||||
import com.fsck.k9.mail.filter.SignSafeOutputStream;
|
||||
|
||||
import org.apache.james.mime4j.codec.QuotedPrintableOutputStream;
|
||||
import org.apache.james.mime4j.util.MimeUtil;
|
||||
|
||||
|
@ -34,6 +36,12 @@ public class TextBody implements Body, SizeAware {
|
|||
this.mBody = body;
|
||||
}
|
||||
|
||||
private static boolean signSafe = false;
|
||||
|
||||
public static void setSignSafe(boolean signable){
|
||||
signSafe = signable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
||||
if (mBody != null) {
|
||||
|
@ -41,6 +49,9 @@ public class TextBody implements Body, SizeAware {
|
|||
if (MimeUtil.ENC_8BIT.equalsIgnoreCase(mEncoding)) {
|
||||
out.write(bytes);
|
||||
} else {
|
||||
if (signSafe){
|
||||
out = new SignSafeOutputStream(out);
|
||||
}
|
||||
QuotedPrintableOutputStream qp = new QuotedPrintableOutputStream(out, false);
|
||||
qp.write(bytes);
|
||||
qp.flush();
|
||||
|
|
Loading…
Reference in a new issue