Don't base64 encode attachments of type message/rfc822.

The problem:  Receive a message with an attachment of type message/rfc822
and forward it.  When the message is sent, K-9 Mail uses base64 encoding
for the attachment.  (Alternatively, you could compose a new message and
add such an attachment from a file using a filing-picking app, but that is
not 100% effective because the app may not choose the correct
message/rfc822 MIME type for the attachment.)

Such encoding is prohibited per RFC 2046 (5.2.1) and RFC 2045 (6.4).  Only
8bit or 7bit encoding is permitted for attachments of type message/rfc822.

Thunderbird refuses to decode such attachments.  All that is shown is the
base64 encoded body.

This commit implements LocalAttachmentBody.setEncoding.  If an attachment
to a newly composed message is itself a message, then setEncoding("8bit")
is called, otherwise setEncoding("base64")  is called for the attachment.
Similar behavior occurs when an attachment is retrieved from LocalStore.

The setEncoding method was added to the Body interface, since all
implementations of Body now declare the method.

The problem here differs from that in the preceding commit:  Here, the
encoding problem occurs on sending, not on receipt.  Here, the entire
message (headers and body) is base64 encoded, not just the body.  Here,
the headers correctly identify the encoding used;  it's just that the RFC
does not permit such encoding of attached messages.  The problem here
could in fact occur in combination with the preceding problem.
This commit is contained in:
Joe Steele 2013-09-01 16:25:09 -04:00
parent 1d1db50a9f
commit 77407eb5b7
3 changed files with 33 additions and 9 deletions

View File

@ -78,10 +78,10 @@ import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.TextBody;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBody;
import com.fsck.k9.view.MessageWebView;
import org.apache.james.mime4j.codec.EncoderUtil;
import org.apache.james.mime4j.util.MimeUtil;
import org.htmlcleaner.CleanerProperties;
import org.htmlcleaner.HtmlCleaner;
import org.htmlcleaner.SimpleHtmlSerializer;
@ -1473,9 +1473,12 @@ public class MessageCompose extends K9Activity implements OnClickListener {
private void addAttachmentsToMessage(final MimeMultipart mp) throws MessagingException {
for (int i = 0, count = mAttachments.getChildCount(); i < count; i++) {
Attachment attachment = (Attachment) mAttachments.getChildAt(i).getTag();
String contentType = attachment.contentType;
String encoding = (MimeUtil.isMessage(contentType)? "8bit" : "base64");
MimeBodyPart bp = new MimeBodyPart(
new LocalStore.LocalAttachmentBody(attachment.uri, getApplication()));
LocalAttachmentBody body = new LocalAttachmentBody(attachment.uri, getApplication());
MimeBodyPart bp = new MimeBodyPart(body);
body.setEncoding(encoding);
/*
* Correctly encode the filename here. Otherwise the whole
@ -1483,11 +1486,11 @@ public class MessageCompose extends K9Activity implements OnClickListener {
* MimeHeader.writeTo().
*/
bp.addHeader(MimeHeader.HEADER_CONTENT_TYPE, String.format("%s;\n name=\"%s\"",
attachment.contentType,
contentType,
EncoderUtil.encodeIfNecessary(attachment.name,
EncoderUtil.Usage.WORD_ENTITY, 7)));
bp.addHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
bp.addHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, encoding);
/*
* TODO: Oh the joys of MIME...

View File

@ -5,7 +5,10 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import com.fsck.k9.mail.store.UnavailableStorageException;
public interface Body {
public InputStream getInputStream() throws MessagingException;
public void setEncoding(String encoding) throws UnavailableStorageException;
public void writeTo(OutputStream out) throws IOException, MessagingException;
}

View File

@ -25,6 +25,7 @@ import java.util.UUID;
import java.util.regex.Pattern;
import org.apache.commons.io.IOUtils;
import org.apache.james.mime4j.util.MimeUtil;
import android.app.Application;
import android.content.ContentResolver;
@ -1936,6 +1937,7 @@ public class LocalStore extends Store implements Serializable {
String contentUri = cursor.getString(5);
String contentId = cursor.getString(6);
String contentDisposition = cursor.getString(7);
String encoding = (MimeUtil.isMessage(type)? "8bit" : "base64");
Body body = null;
if (contentDisposition == null) {
@ -1944,10 +1946,11 @@ public class LocalStore extends Store implements Serializable {
if (contentUri != null) {
body = new LocalAttachmentBody(Uri.parse(contentUri), mApplication);
((LocalAttachmentBody) body).setEncoding(encoding);
}
MimeBodyPart bp = new LocalAttachmentBodyPart(body, id);
bp.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
bp.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, encoding);
if (name != null) {
bp.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
String.format("%s;\n name=\"%s\"",
@ -3988,6 +3991,7 @@ public class LocalStore extends Store implements Serializable {
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
private Application mApplication;
private Uri mUri;
private String mEncoding;
public LocalAttachmentBody(Uri uri, Application application) {
mApplication = application;
@ -4011,11 +4015,21 @@ public class LocalStore extends Store implements Serializable {
public void writeTo(OutputStream out) throws IOException, MessagingException {
InputStream in = getInputStream();
try {
Base64OutputStream base64Out = new Base64OutputStream(out);
// TODO: attachments of type rfc822 are sent with 8bit encoding
// without regard to the SMTP server's support of 8BITMIME, whereas
// strict protocol compliance requires that they be converted to
// 7bit in the (unlikely) event that 8BITMIME is not supported.
if (MimeUtil.isBase64Encoding(mEncoding)) {
out = new Base64OutputStream(out);
}
try {
IOUtils.copy(in, base64Out);
IOUtils.copy(in, out);
} finally {
base64Out.close();
if (MimeUtil.isBase64Encoding(mEncoding)) {
out.close();
}
}
} finally {
in.close();
@ -4025,6 +4039,10 @@ public class LocalStore extends Store implements Serializable {
public Uri getContentUri() {
return mUri;
}
public void setEncoding(String encoding) {
mEncoding = encoding;
}
}
static class ThreadInfo {