Merge pull request #3352 from k9mail/LimitMimeEncodeOnlyFilename

RFC 2184 parameter value encoding & ignored test for wrapping headers
This commit is contained in:
Philip 2018-04-22 07:57:22 +01:00 committed by GitHub
commit 9b00cc431b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 158 additions and 55 deletions

View file

@ -226,43 +226,60 @@ public abstract class MessageBuilder {
Body body = new TempFileBody(attachment.filename);
MimeBodyPart bp = new MimeBodyPart(body);
/*
* Correctly encode the filename here. Otherwise the whole
* header value (all parameters at once) will be encoded by
* MimeHeader.writeTo().
*/
bp.addHeader(MimeHeader.HEADER_CONTENT_TYPE, String.format("%s;\r\n name=\"%s\"",
attachment.contentType,
EncoderUtil.encodeIfNecessary(attachment.name,
EncoderUtil.Usage.WORD_ENTITY, 7)));
if (!MimeUtil.isMessage(attachment.contentType)) {
bp.setEncoding(MimeUtility.getEncodingforType(attachment.contentType));
}
/*
* TODO: Oh the joys of MIME...
*
* From RFC 2183 (The Content-Disposition Header Field):
* "Parameter values longer than 78 characters, or which
* contain non-ASCII characters, MUST be encoded as specified
* in [RFC 2184]."
*
* Example:
*
* Content-Type: application/x-stuff
* title*1*=us-ascii'en'This%20is%20even%20more%20
* title*2*=%2A%2A%2Afun%2A%2A%2A%20
* title*3="isn't it!"
*/
bp.addHeader(MimeHeader.HEADER_CONTENT_DISPOSITION, String.format(Locale.US,
"attachment;\r\n filename=\"%s\";\r\n size=%d",
attachment.name, attachment.size));
addContentType(bp, attachment.contentType, attachment.name);
addContentDisposition(bp, attachment.name, attachment.size);
mp.addBodyPart(bp);
}
}
/*
* Content-Type is defined in RFC 2045
*
* Example:
*
* Content-Type: text/plain; charset=us-ascii (Plain text)
*
* TODO: RFC 2231/2184 long parameter encoding
* Example:
*
* Content-Type: application/x-stuff
* title*1*=us-ascii'en'This%20is%20even%20more%20
* title*2*=%2A%2A%2Afun%2A%2A%2A%20
* title*3="isn't it!"
*/
private void addContentType(MimeBodyPart bp, String contentType, String name) throws MessagingException {
/*
* Correctly encode the filename here. Otherwise the whole
* header value (all parameters at once) will be encoded by
* MimeHeader.writeTo().
*/
bp.addHeader(MimeHeader.HEADER_CONTENT_TYPE, String.format("%s;\r\n name=\"%s\"",
contentType,
EncoderUtil.encodeIfNecessary(name,
EncoderUtil.Usage.WORD_ENTITY, 7)));
if (!MimeUtil.isMessage(contentType)) {
bp.setEncoding(MimeUtility.getEncodingforType(contentType));
}
}
/*
* TODO: RFC 2231/2184 long parameter encoding
*
* From RFC 2183 (The Content-Disposition Header Field):
* "Parameter values longer than 78 characters, or which
* contain non-ASCII characters, MUST be encoded as specified
* in [RFC 2184]."
*
*/
private void addContentDisposition(MimeBodyPart bp, String name, Long size) {
bp.addHeader(MimeHeader.HEADER_CONTENT_DISPOSITION, String.format(Locale.US,
"attachment;\r\n filename=\"%s\";\r\n size=%d",
EncoderUtil.encodeIfNecessary(name, EncoderUtil.Usage.WORD_ENTITY, 7),
size));
}
/**
* Build the Body that will contain the text of the message. We'll decide where to
* include it later. Draft messages are treated somewhat differently in that signatures are not

View file

@ -29,6 +29,7 @@ import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.message.MessageBuilder.Callback;
import com.fsck.k9.message.quote.InsertableHtmlContent;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.robolectric.Robolectric;
@ -45,23 +46,26 @@ import static org.mockito.Mockito.when;
public class MessageBuilderTest extends RobolectricTest {
public static final String TEST_MESSAGE_TEXT = "soviet message\r\ntext ☭";
public static final String TEST_ATTACHMENT_TEXT = "text data in attachment";
public static final String TEST_SUBJECT = "test_subject";
public static final Address TEST_IDENTITY_ADDRESS = new Address("test@example.org", "tester");
public static final Address[] TEST_TO = new Address[] {
new Address("to1@example.org", "recip 1"), new Address("to2@example.org", "recip 2")
private static final String TEST_MESSAGE_TEXT = "soviet message\r\ntext ☭";
private static final String TEST_ATTACHMENT_TEXT = "text data in attachment";
private static final String TEST_SUBJECT = "test_subject";
private static final Address TEST_IDENTITY_ADDRESS = new Address("test@example.org", "tester");
private static final Address[] TEST_TO = new Address[] {
new Address("to1@example.org", "recip 1"),
new Address("to2@example.org", "recip 2")
};
public static final Address[] TEST_CC = new Address[] { new Address("cc@example.org", "cc recip") };
public static final Address[] TEST_BCC = new Address[] { new Address("bcc@example.org", "bcc recip") };
public static final String TEST_MESSAGE_ID = "<00000000-0000-007B-0000-0000000000EA@example.org>";
public static final Date SENT_DATE = new Date(10000000000L);
private static final Address[] TEST_CC = new Address[] {
new Address("cc@example.org", "cc recip") };
private static final Address[] TEST_BCC = new Address[] {
new Address("bcc@example.org", "bcc recip") };
private static final String TEST_MESSAGE_ID = "<00000000-0000-007B-0000-0000000000EA@example.org>";
private static final Date SENT_DATE = new Date(10000000000L);
public static final String BOUNDARY_1 = "----boundary1";
public static final String BOUNDARY_2 = "----boundary2";
public static final String BOUNDARY_3 = "----boundary3";
private static final String BOUNDARY_1 = "----boundary1";
private static final String BOUNDARY_2 = "----boundary2";
private static final String BOUNDARY_3 = "----boundary3";
public static final String MESSAGE_HEADERS = "" +
private static final String MESSAGE_HEADERS = "" +
"Date: Sun, 26 Apr 1970 17:46:40 +0000\r\n" +
"From: tester <test@example.org>\r\n" +
"To: recip 1 <to1@example.org>,recip 2 <to2@example.org>\r\n" +
@ -73,16 +77,16 @@ public class MessageBuilderTest extends RobolectricTest {
"References: references\r\n" +
"Message-ID: " + TEST_MESSAGE_ID + "\r\n" +
"MIME-Version: 1.0\r\n";
public static final String MESSAGE_CONTENT = "" +
private static final String MESSAGE_CONTENT = "" +
"Content-Type: text/plain;\r\n" +
" charset=utf-8\r\n" +
"Content-Transfer-Encoding: quoted-printable\r\n" +
"\r\n" +
"soviet message\r\n" +
"text =E2=98=AD";
public static final String MESSAGE_CONTENT_WITH_ATTACH = "" +
private static final String MESSAGE_CONTENT_WITH_ATTACH = "" +
"Content-Type: multipart/mixed; boundary=\"" + BOUNDARY_1 + "\"\r\n" +
"Content-Transfer-Encoding: 7bit\r\n" +
"\r\n" +
@ -104,8 +108,57 @@ public class MessageBuilderTest extends RobolectricTest {
"dGV4dCBkYXRhIGluIGF0dGFjaG1lbnQ=\r\n" +
"\r\n" +
"--" + BOUNDARY_1 + "--\r\n";
public static final String MESSAGE_CONTENT_WITH_MESSAGE_ATTACH = "" +
private static final String MESSAGE_CONTENT_WITH_LONG_CONTENT_TYPE =
"Content-Type: multipart/mixed; boundary=\"" + BOUNDARY_1 + "\"\r\n" +
"Content-Transfer-Encoding: 7bit\r\n" +
"\r\n" +
"--" + BOUNDARY_1 + "\r\n" +
"Content-Type: text/plain;\r\n" +
" title*1*=1234567891123456789212345678931234567894123456789\r\n" +
" title*2*=5123456789612345678971234567898123456789091234567890;\r\n" +
" charset=utf-8\r\n" +
"Content-Transfer-Encoding: quoted-printable\r\n" +
"\r\n" +
"soviet message\r\n" +
"text =E2=98=AD\r\n" +
"--" + BOUNDARY_1 + "\r\n" +
"Content-Type: text/plain;\r\n" +
" name=\"attach.txt\"\r\n" +
"Content-Transfer-Encoding: base64\r\n" +
"Content-Disposition: attachment;\r\n" +
" filename=\"attach.txt\";\r\n" +
" size=23\r\n" +
"\r\n" +
"dGV4dCBkYXRhIGluIGF0dGFjaG1lbnQ=\r\n" +
"\r\n" +
"--" + BOUNDARY_1 + "--\r\n";
private static final String ATTACHMENT_FILENAME_NON_ASCII = "テスト文書.txt";
private static final String MESSAGE_CONTENT_WITH_ATTACH_NON_ASCII_FILENAME = "" +
"Content-Type: multipart/mixed; boundary=\"" + BOUNDARY_1 + "\"\r\n" +
"Content-Transfer-Encoding: 7bit\r\n" +
"\r\n" +
"--" + BOUNDARY_1 + "\r\n" +
"Content-Type: text/plain;\r\n" +
" charset=utf-8\r\n" +
"Content-Transfer-Encoding: quoted-printable\r\n" +
"\r\n" +
"soviet message\r\n" +
"text =E2=98=AD\r\n" +
"--" + BOUNDARY_1 + "\r\n" +
"Content-Type: text/plain;\r\n" +
" name=\"=?UTF-8?B?44OG44K544OI5paH5pu4LnR4dA==?=\"\r\n" +
"Content-Transfer-Encoding: base64\r\n" +
"Content-Disposition: attachment;\r\n" +
" filename=\"=?UTF-8?B?44OG44K544OI5paH5pu4LnR4dA==?=\";\r\n" +
" size=23\r\n" +
"\r\n" +
"dGV4dCBkYXRhIGluIGF0dGFjaG1lbnQ=\r\n" +
"\r\n" +
"--" + BOUNDARY_1 + "--\r\n";
private static final String MESSAGE_CONTENT_WITH_MESSAGE_ATTACH = "" +
"Content-Type: multipart/mixed; boundary=\"" + BOUNDARY_1 + "\"\r\n" +
"Content-Transfer-Encoding: 7bit\r\n" +
"\r\n" +
@ -165,7 +218,8 @@ public class MessageBuilderTest extends RobolectricTest {
@Test
public void build_withAttachment_shouldSucceed() throws Exception {
MessageBuilder messageBuilder = createSimpleMessageBuilder();
Attachment attachment = createAttachmentWithContent("text/plain", "attach.txt", TEST_ATTACHMENT_TEXT);
Attachment attachment = createAttachmentWithContent(
"text/plain", "attach.txt", TEST_ATTACHMENT_TEXT);
messageBuilder.setAttachments(Collections.singletonList(attachment));
messageBuilder.buildAsync(callback);
@ -174,6 +228,36 @@ public class MessageBuilderTest extends RobolectricTest {
assertEquals(MESSAGE_HEADERS + MESSAGE_CONTENT_WITH_ATTACH, getMessageContents(message));
}
@Ignore("RFC2231/2184 not implemented") @Test
public void build_withAttachment_longContentType_shouldSucceed() throws Exception {
MessageBuilder messageBuilder = createSimpleMessageBuilder();
Attachment attachment = createAttachmentWithContent(
"text/plain;title=1234567891123456789212345678931234567894123456789" +
"5123456789612345678971234567898123456789091234567890",
"attach.txt", TEST_ATTACHMENT_TEXT);
messageBuilder.setAttachments(Collections.singletonList(attachment));
messageBuilder.buildAsync(callback);
MimeMessage message = getMessageFromCallback();
assertEquals(MESSAGE_HEADERS + MESSAGE_CONTENT_WITH_LONG_CONTENT_TYPE,
getMessageContents(message));
}
@Test
public void build_withAttachment_nonAscii_shouldSucceed() throws Exception {
MessageBuilder messageBuilder = createSimpleMessageBuilder();
Attachment attachment = createAttachmentWithContent(
"text/plain", ATTACHMENT_FILENAME_NON_ASCII, TEST_ATTACHMENT_TEXT);
messageBuilder.setAttachments(Collections.singletonList(attachment));
messageBuilder.buildAsync(callback);
MimeMessage message = getMessageFromCallback();
assertEquals(MESSAGE_HEADERS + MESSAGE_CONTENT_WITH_ATTACH_NON_ASCII_FILENAME,
getMessageContents(message));
}
@Test
public void build_usingHtmlFormat_shouldUseMultipartAlternativeInCorrectOrder() {
MessageBuilder messageBuilder = createHtmlMessageBuilder();
@ -192,13 +276,15 @@ public class MessageBuilderTest extends RobolectricTest {
@Test
public void build_withMessageAttachment_shouldAttachAsMessageRfc822() throws Exception {
MessageBuilder messageBuilder = createSimpleMessageBuilder();
Attachment attachment = createAttachmentWithContent("message/rfc822", "attach.txt", TEST_ATTACHMENT_TEXT);
Attachment attachment = createAttachmentWithContent(
"message/rfc822", "attach.txt", TEST_ATTACHMENT_TEXT);
messageBuilder.setAttachments(Collections.singletonList(attachment));
messageBuilder.buildAsync(callback);
MimeMessage message = getMessageFromCallback();
assertEquals(MESSAGE_HEADERS + MESSAGE_CONTENT_WITH_MESSAGE_ATTACH, getMessageContents(message));
assertEquals(MESSAGE_HEADERS + MESSAGE_CONTENT_WITH_MESSAGE_ATTACH,
getMessageContents(message));
}
@Test