clean up TextBody, make it slightly more failfast

Previously, TextBody supported only 8bit or quoted printable encodings,
defaulting to quoted printable if the encoding was not equal to "8bit".
This behavior is now changed to throw a runtime exception when an
unsupported encoding has been selected. The `setEncoding` method also
throws if an unsupported encoding is selected.
This commit is contained in:
Vincent Breitmoser 2016-08-08 15:12:57 +02:00
parent b0e5912891
commit 08ef3d5ce5
4 changed files with 53 additions and 44 deletions

View file

@ -47,7 +47,7 @@ public class MessageExtractor {
if ((part != null) && (part.getBody() != null)) {
final Body body = part.getBody();
if (body instanceof TextBody) {
return ((TextBody)body).getText();
return ((TextBody)body).getRawText();
}
final String mimeType = part.getMimeType();
if (mimeType != null && MimeUtility.mimeTypeMatches(mimeType, "text/*") ||

View file

@ -2,6 +2,7 @@
package com.fsck.k9.mail.internet;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.K9MailLib;
import com.fsck.k9.mail.MessagingException;
import java.io.ByteArrayInputStream;
@ -10,6 +11,9 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import android.support.annotation.Nullable;
import android.util.Log;
import com.fsck.k9.mail.filter.CountingOutputStream;
import com.fsck.k9.mail.filter.SignSafeOutputStream;
@ -17,102 +21,107 @@ import org.apache.james.mime4j.codec.QuotedPrintableOutputStream;
import org.apache.james.mime4j.util.MimeUtil;
public class TextBody implements Body, SizeAware {
/**
* Immutable empty byte array
*/
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
private final String mBody;
private String mEncoding;
private String mCharset = "UTF-8";
private final String text;
private String encoding;
private String charset = "UTF-8";
// Length of the message composed (as opposed to quoted). I don't like the name of this variable and am open to
// suggestions as to what it should otherwise be. -achen 20101207
private Integer mComposedMessageLength;
@Nullable
private Integer composedMessageLength;
// Offset from position 0 where the composed message begins.
private Integer mComposedMessageOffset;
@Nullable
private Integer composedMessageOffset;
public TextBody(String body) {
this.mBody = body;
this.text = body;
}
@Override
public void writeTo(OutputStream out) throws IOException, MessagingException {
if (mBody != null) {
byte[] bytes = mBody.getBytes(mCharset);
if (MimeUtil.ENC_8BIT.equalsIgnoreCase(mEncoding)) {
out.write(bytes);
} else {
if (text != null) {
byte[] bytes = text.getBytes(charset);
if (MimeUtil.ENC_QUOTED_PRINTABLE.equalsIgnoreCase(encoding)) {
SignSafeOutputStream signSafeOutputStream = new SignSafeOutputStream(out);
QuotedPrintableOutputStream signSafeQuotedPrintableOutputStream =
new QuotedPrintableOutputStream(signSafeOutputStream, false);
signSafeQuotedPrintableOutputStream.write(bytes);
signSafeQuotedPrintableOutputStream.flush();
signSafeOutputStream.flush();
} else if (MimeUtil.ENC_8BIT.equalsIgnoreCase(encoding)) {
out.write(bytes);
} else {
throw new IllegalStateException("Cannot get size for encoding!");
}
}
}
/**
* Get the text of the body in it's unencoded format.
* @return
*/
public String getText() {
return mBody;
public String getRawText() {
return text;
}
/**
* Returns an InputStream that reads this body's text.
*/
@Override
public InputStream getInputStream() throws MessagingException {
try {
byte[] b;
if (mBody != null) {
b = mBody.getBytes(mCharset);
if (text != null) {
b = text.getBytes(charset);
} else {
b = EMPTY_BYTE_ARRAY;
}
return new ByteArrayInputStream(b);
} catch (UnsupportedEncodingException uee) {
Log.e(K9MailLib.LOG_TAG, "Unsupported charset: " + charset, uee);
return null;
}
}
@Override
public void setEncoding(String encoding) {
mEncoding = encoding;
boolean isSupportedEncoding = MimeUtil.ENC_QUOTED_PRINTABLE.equalsIgnoreCase(encoding) ||
MimeUtil.ENC_8BIT.equalsIgnoreCase(encoding);
if (!isSupportedEncoding) {
throw new IllegalArgumentException("Cannot encode to " + encoding);
}
this.encoding = encoding;
}
public void setCharset(String charset) {
mCharset = charset;
this.charset = charset;
}
@Nullable
public Integer getComposedMessageLength() {
return mComposedMessageLength;
return composedMessageLength;
}
public void setComposedMessageLength(Integer composedMessageLength) {
this.mComposedMessageLength = composedMessageLength;
public void setComposedMessageLength(@Nullable Integer composedMessageLength) {
this.composedMessageLength = composedMessageLength;
}
@Nullable
public Integer getComposedMessageOffset() {
return mComposedMessageOffset;
return composedMessageOffset;
}
public void setComposedMessageOffset(Integer composedMessageOffset) {
this.mComposedMessageOffset = composedMessageOffset;
public void setComposedMessageOffset(@Nullable Integer composedMessageOffset) {
this.composedMessageOffset = composedMessageOffset;
}
@Override
public long getSize() {
try {
byte[] bytes = mBody.getBytes(mCharset);
byte[] bytes = text.getBytes(charset);
if (MimeUtil.ENC_8BIT.equalsIgnoreCase(mEncoding)) {
if (MimeUtil.ENC_QUOTED_PRINTABLE.equalsIgnoreCase(encoding)) {
return getLengthWhenQuotedPrintableEncoded(bytes);
} else if (MimeUtil.ENC_8BIT.equalsIgnoreCase(encoding)) {
return bytes.length;
} else {
return getLengthWhenQuotedPrintableEncoded(bytes);
throw new IllegalStateException("Cannot get size for encoding!");
}
} catch (IOException e) {
throw new RuntimeException("Couldn't get body size", e);
@ -134,6 +143,6 @@ public class TextBody implements Body, SizeAware {
}
public String getEncoding() {
return mEncoding;
return encoding;
}
}

View file

@ -49,7 +49,7 @@ public class IdentityHeaderBuilder {
appendValue(IdentityField.OFFSET, body.getComposedMessageOffset());
} else {
// If not, calculate it now.
appendValue(IdentityField.LENGTH, body.getText().length());
appendValue(IdentityField.LENGTH, body.getRawText().length());
appendValue(IdentityField.OFFSET, 0);
}
@ -66,7 +66,7 @@ public class IdentityHeaderBuilder {
appendValue(IdentityField.PLAIN_OFFSET, composedMessageOffset);
} else {
// If not, calculate it now.
appendValue(IdentityField.PLAIN_LENGTH, body.getText().length());
appendValue(IdentityField.PLAIN_LENGTH, body.getRawText().length());
appendValue(IdentityField.PLAIN_OFFSET, 0);
}
}

View file

@ -109,10 +109,10 @@ public class TextBodyBuilderTest {
TextBody textBody = textBodyBuilder.buildTextPlain();
assertThat(textBody, instanceOf(TextBody.class));
assertThat(textBody.getText(), is(expectedText));
assertThat(textBody.getRawText(), is(expectedText));
assertThat(textBody.getComposedMessageLength(), is(expectedMessageLength));
assertThat(textBody.getComposedMessageOffset(), is(expectedMessagePosition));
assertThat(textBody.getText().substring(expectedMessagePosition, expectedMessagePosition + expectedMessageLength),
assertThat(textBody.getRawText().substring(expectedMessagePosition, expectedMessagePosition + expectedMessageLength),
is("message content"));
}
@ -285,7 +285,7 @@ public class TextBodyBuilderTest {
TextBody textBody = textBodyBuilder.buildTextHtml();
assertThat(textBody, instanceOf(TextBody.class));
assertThat(textBody.getText(), is(expectedText));
assertThat(textBody.getRawText(), is(expectedText));
assertThat(textBody.getComposedMessageLength(), is(expectedMessageLength));
assertThat(textBody.getComposedMessageOffset(), is(expectedMessagePosition));
assertThat(insertableHtmlContent.toDebugString(), is(expectedHtmlContent));