package com.fsck.k9.mail.internet; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import android.support.annotation.Nullable; import com.fsck.k9.mail.Body; import com.fsck.k9.mail.MessagingException; 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; import timber.log.Timber; public class TextBody implements Body, SizeAware { private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; 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 @Nullable private Integer composedMessageLength; // Offset from position 0 where the composed message begins. @Nullable private Integer composedMessageOffset; public TextBody(String body) { this.text = body; } @Override public void writeTo(OutputStream out) throws IOException, MessagingException { if (text != null) { byte[] bytes = text.getBytes(charset); if (MimeUtil.ENC_QUOTED_PRINTABLE.equalsIgnoreCase(encoding)) { writeSignSafeQuotedPrintable(out, bytes); } else if (MimeUtil.ENC_8BIT.equalsIgnoreCase(encoding)) { out.write(bytes); } else { throw new IllegalStateException("Cannot get size for encoding!"); } } } public String getRawText() { return text; } @Override public InputStream getInputStream() throws MessagingException { try { byte[] b; if (text != null) { b = text.getBytes(charset); } else { b = EMPTY_BYTE_ARRAY; } return new ByteArrayInputStream(b); } catch (UnsupportedEncodingException uee) { Timber.e(uee, "Unsupported charset: %s", charset); return null; } } @Override public void setEncoding(String 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) { this.charset = charset; } @Nullable public Integer getComposedMessageLength() { return composedMessageLength; } public void setComposedMessageLength(@Nullable Integer composedMessageLength) { this.composedMessageLength = composedMessageLength; } @Nullable public Integer getComposedMessageOffset() { return composedMessageOffset; } public void setComposedMessageOffset(@Nullable Integer composedMessageOffset) { this.composedMessageOffset = composedMessageOffset; } @Override public long getSize() { try { byte[] bytes = text.getBytes(charset); if (MimeUtil.ENC_QUOTED_PRINTABLE.equalsIgnoreCase(encoding)) { return getLengthWhenQuotedPrintableEncoded(bytes); } else if (MimeUtil.ENC_8BIT.equalsIgnoreCase(encoding)) { return bytes.length; } else { throw new IllegalStateException("Cannot get size for encoding!"); } } catch (IOException e) { throw new RuntimeException("Couldn't get body size", e); } } private long getLengthWhenQuotedPrintableEncoded(byte[] bytes) throws IOException { CountingOutputStream countingOutputStream = new CountingOutputStream(); writeSignSafeQuotedPrintable(countingOutputStream, bytes); return countingOutputStream.getCount(); } private void writeSignSafeQuotedPrintable(OutputStream out, byte[] bytes) throws IOException { SignSafeOutputStream signSafeOutputStream = new SignSafeOutputStream(out); try { QuotedPrintableOutputStream signSafeQuotedPrintableOutputStream = new QuotedPrintableOutputStream(signSafeOutputStream, false); try { signSafeQuotedPrintableOutputStream.write(bytes); } finally { signSafeQuotedPrintableOutputStream.close(); } } finally { signSafeOutputStream.close(); } } public String getEncoding() { return encoding; } }