Merge pull request #3912 from k9mail/fix_recipient_headers

Fold recipient headers
This commit is contained in:
cketti 2019-02-14 03:34:10 +01:00 committed by GitHub
commit e65daf5c08
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 162 additions and 115 deletions

View file

@ -43,7 +43,7 @@ class AutocryptTransferMessageCreator(private val stringProvider: AutocryptStrin
message.internalDate = nowDate
message.addSentDate(nowDate, K9.hideTimeZone())
message.setFrom(address)
message.setRecipients(RecipientType.TO, arrayOf(address))
message.setHeader("To", address.toEncodedString())
return message
} catch (e: MessagingException) {

View file

@ -18,6 +18,7 @@ import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.AddressHeaderBuilder;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.message.MessageHeaderParser;
import com.fsck.k9.mailstore.LockableDatabase.DbCallback;
@ -80,9 +81,10 @@ public class LocalMessage extends MimeMessage {
}
}
this.databaseId = cursor.getLong(LocalStore.MSG_INDEX_ID);
this.setRecipients(RecipientType.TO, Address.unpack(cursor.getString(LocalStore.MSG_INDEX_TO)));
this.setRecipients(RecipientType.CC, Address.unpack(cursor.getString(LocalStore.MSG_INDEX_CC)));
this.setRecipients(RecipientType.BCC, Address.unpack(cursor.getString(LocalStore.MSG_INDEX_BCC)));
mTo = getAddressesOrNull(Address.unpack(cursor.getString(LocalStore.MSG_INDEX_TO)));
mCc = getAddressesOrNull(Address.unpack(cursor.getString(LocalStore.MSG_INDEX_CC)));
mBcc = getAddressesOrNull(Address.unpack(cursor.getString(LocalStore.MSG_INDEX_BCC)));
headerNeedsUpdating = true;
this.setReplyTo(Address.unpack(cursor.getString(LocalStore.MSG_INDEX_REPLY_TO)));
this.attachmentCount = cursor.getInt(LocalStore.MSG_INDEX_ATTACHMENT_COUNT);
@ -210,36 +212,12 @@ public class LocalMessage extends MimeMessage {
headerNeedsUpdating = true;
}
/*
* For performance reasons, we add headers instead of setting them (see super implementation)
* which removes (expensive) them before adding them
*/
@Override
public void setRecipients(RecipientType type, Address[] addresses) {
if (type == RecipientType.TO) {
if (addresses == null || addresses.length == 0) {
this.mTo = null;
} else {
this.mTo = addresses;
}
} else if (type == RecipientType.CC) {
if (addresses == null || addresses.length == 0) {
this.mCc = null;
} else {
this.mCc = addresses;
}
} else if (type == RecipientType.BCC) {
if (addresses == null || addresses.length == 0) {
this.mBcc = null;
} else {
this.mBcc = addresses;
}
private Address[] getAddressesOrNull(Address[] addresses) {
if (addresses == null || addresses.length == 0) {
return null;
} else {
throw new IllegalArgumentException("Unrecognized recipient type.");
return addresses;
}
headerNeedsUpdating = true;
}
public void setFlagInternal(Flag flag, boolean set) throws MessagingException {
@ -444,9 +422,10 @@ public class LocalMessage extends MimeMessage {
private void updateHeader() {
super.setSubject(subject);
super.setReplyTo(mReplyTo);
super.setRecipients(RecipientType.TO, mTo);
super.setRecipients(RecipientType.CC, mCc);
super.setRecipients(RecipientType.BCC, mBcc);
setRecipients("To", mTo);
setRecipients("CC", mCc);
setRecipients("BCC", mBcc);
if (mFrom != null && mFrom.length > 0) {
super.setFrom(mFrom[0]);
@ -459,6 +438,15 @@ public class LocalMessage extends MimeMessage {
headerNeedsUpdating = false;
}
private void setRecipients(String headerName, Address[] addresses) {
if (addresses == null || addresses.length == 0) {
removeHeader(headerName);
} else {
String headerValue = AddressHeaderBuilder.createHeaderValue(addresses);
setHeader(headerName, headerValue);
}
}
@Override
public boolean equals(Object o) {
if (this == o) {

View file

@ -10,6 +10,7 @@ import android.content.Intent;
import android.os.AsyncTask;
import com.fsck.k9.CoreResourceProvider;
import com.fsck.k9.mail.internet.AddressHeaderBuilder;
import com.fsck.k9.mail.internet.Headers;
import timber.log.Timber;
@ -21,7 +22,6 @@ import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BoundaryGenerator;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.MessageIdGenerator;
import com.fsck.k9.mail.internet.MimeBodyPart;
@ -95,9 +95,10 @@ public abstract class MessageBuilder {
message.addSentDate(sentDate, hideTimeZone);
Address from = new Address(identity.getEmail(), identity.getName());
message.setFrom(from);
message.setRecipients(RecipientType.TO, to);
message.setRecipients(RecipientType.CC, cc);
message.setRecipients(RecipientType.BCC, bcc);
setRecipients(message, "To", to);
setRecipients(message, "CC", cc);
setRecipients(message, "BCC", bcc);
message.setSubject(subject);
if (requestReadReceipt) {
@ -133,7 +134,14 @@ public abstract class MessageBuilder {
message.setFlag(Flag.DRAFT, true);
}
}
private void setRecipients(MimeMessage message, String headerName, Address[] addresses) {
if (addresses != null && addresses.length > 0) {
String headerValue = AddressHeaderBuilder.createHeaderValue(addresses);
message.setHeader(headerName, headerValue);
}
}
protected MimeMultipart createMimeMultipart() {
String boundary = boundaryGenerator.generateBoundary();
return new MimeMultipart(boundary);

View file

@ -7,6 +7,7 @@ import com.fsck.k9.RobolectricTest
import com.fsck.k9.mail.Address
import com.fsck.k9.mail.Message
import com.fsck.k9.mail.Message.RecipientType
import com.fsck.k9.mail.internet.AddressHeaderBuilder
import com.fsck.k9.mail.internet.MimeMessage
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@ -128,11 +129,21 @@ class IdentityHelperTest : RobolectricTest() {
private fun messageWithRecipients(vararg recipients: Pair<RecipientType, String>): Message {
return MimeMessage().apply {
for ((recipientType, email) in recipients) {
setRecipients(recipientType, arrayOf(Address(email)))
val headerName = recipientType.toHeaderName()
addHeader(headerName, AddressHeaderBuilder.createHeaderValue(arrayOf(Address(email))))
}
}
}
private fun RecipientType.toHeaderName() = when (this) {
RecipientType.TO -> "To"
RecipientType.CC -> "Cc"
RecipientType.BCC -> "Bcc"
RecipientType.X_ORIGINAL_TO -> "X-Original-To"
RecipientType.DELIVERED_TO -> "Delivered-To"
RecipientType.X_ENVELOPE_TO -> "X-Envelope-To"
}
companion object {
const val DEFAULT_ADDRESS = "default@example.org"
const val IDENTITY_1_ADDRESS = "identity1@example.org"

View file

@ -11,7 +11,6 @@ import com.fsck.k9.backend.api.FolderInfo
import com.fsck.k9.mail.Address
import com.fsck.k9.mail.Flag
import com.fsck.k9.mail.Folder.FolderType
import com.fsck.k9.mail.Message
import com.fsck.k9.mail.internet.MimeMessage
import com.fsck.k9.mail.internet.MimeMessageHelper
import com.fsck.k9.mail.internet.TextBody
@ -101,7 +100,7 @@ class K9BackendFolderTest : K9RobolectricTest() {
val message = MimeMessage().apply {
subject = "Test message"
setFrom(Address("alice@domain.example"))
setRecipient(Message.RecipientType.TO, Address("bob@domain.example"))
setHeader("To", "bob@domain.example")
MimeMessageHelper.setBody(this, TextBody("Hello Bob!"))
uid = messageServerId

View file

@ -19,7 +19,6 @@ import com.fsck.k9.TestCoreResourceProvider;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MessageExtractor;
@ -244,7 +243,7 @@ public class MessageViewInfoExtractorTest extends K9RobolectricTest {
// Create message/rfc822 body
MimeMessage innerMessage = new MimeMessage();
innerMessage.addSentDate(new Date(112, 2, 17), false);
innerMessage.setRecipients(RecipientType.TO, new Address[] { new Address("to@example.com") });
innerMessage.setHeader("To", "to@example.com");
innerMessage.setSubject("Subject");
innerMessage.setFrom(new Address("from@example.com"));
MimeMessageHelper.setBody(innerMessage, innerBody);

View file

@ -65,7 +65,7 @@ public class MessageBuilderTest extends RobolectricTest {
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" +
"To: recip 1 <to1@example.org>, recip 2 <to2@example.org>\r\n" +
"CC: cc recip <cc@example.org>\r\n" +
"BCC: bcc recip <bcc@example.org>\r\n" +
"Subject: test_subject\r\n" +

View file

@ -89,14 +89,6 @@ public abstract class Message implements Part, Body {
public abstract Address[] getRecipients(RecipientType type);
public abstract void setRecipients(RecipientType type, Address[] addresses);
public void setRecipient(RecipientType type, Address address) {
setRecipients(type, new Address[] {
address
});
}
public abstract Address[] getFrom();
public abstract void setFrom(Address from);

View file

@ -0,0 +1,35 @@
package com.fsck.k9.mail.internet
import com.fsck.k9.mail.Address
/**
* Encode and fold email addresses for use in header values.
*/
object AddressHeaderBuilder {
@JvmStatic
fun createHeaderValue(addresses: Array<Address>): String {
require(addresses.isNotEmpty()) { "addresses must not be empty" }
return buildString {
var lineLength = 0
for ((index, address) in addresses.withIndex()) {
val encodedAddress = address.toEncodedString()
val encodedAddressLength = encodedAddress.length
if (index > 0 && lineLength + 2 + encodedAddressLength + 1 > RECOMMENDED_MAX_LINE_LENGTH) {
append(",$CRLF ")
append(encodedAddress)
lineLength = encodedAddressLength + 1
} else {
if (index > 0) {
append(", ")
lineLength += 2
}
append(encodedAddress)
lineLength += encodedAddressLength
}
}
}
}
}

View file

@ -1,6 +1,9 @@
package com.fsck.k9.mail.internet
// RFC 5322, section 2.1.1
internal const val RECOMMENDED_MAX_LINE_LENGTH = 78
// RFC 2045: tspecials := "(" / ")" / "<" / ">" / "@" / "," / ";" / ":" / "\" / <"> / "/" / "[" / "]" / "?" / "="
private val TSPECIALS = charArrayOf('(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=')

View file

@ -252,61 +252,6 @@ public class MimeMessage extends Message {
throw new IllegalArgumentException("Unrecognized recipient type.");
}
@Override
public void setRecipients(RecipientType type, Address[] addresses) {
if (type == RecipientType.TO) {
if (addresses == null || addresses.length == 0) {
removeHeader("To");
this.mTo = null;
} else {
setHeader("To", Address.toEncodedString(addresses));
this.mTo = addresses;
}
} else if (type == RecipientType.CC) {
if (addresses == null || addresses.length == 0) {
removeHeader("CC");
this.mCc = null;
} else {
setHeader("CC", Address.toEncodedString(addresses));
this.mCc = addresses;
}
} else if (type == RecipientType.BCC) {
if (addresses == null || addresses.length == 0) {
removeHeader("BCC");
this.mBcc = null;
} else {
setHeader("BCC", Address.toEncodedString(addresses));
this.mBcc = addresses;
}
} else if (type == RecipientType.X_ORIGINAL_TO) {
if (addresses == null || addresses.length == 0) {
removeHeader("X-Original-To");
this.xOriginalTo = null;
} else {
setHeader("X-Original-To", Address.toEncodedString(addresses));
this.xOriginalTo = addresses;
}
} else if (type == RecipientType.DELIVERED_TO) {
if (addresses == null || addresses.length == 0) {
removeHeader("Delivered-To");
this.deliveredTo = null;
} else {
setHeader("Delivered-To", Address.toEncodedString(addresses));
this.deliveredTo = addresses;
}
} else if (type == RecipientType.X_ENVELOPE_TO) {
if (addresses == null || addresses.length == 0) {
removeHeader("X-Envelope-To");
this.xEnvelopeTo = null;
} else {
setHeader("X-Envelope-To", Address.toEncodedString(addresses));
this.xEnvelopeTo = addresses;
}
} else {
throw new IllegalStateException("Unrecognized recipient type.");
}
}
/**
* Returns the unfolded, decoded value of the Subject header.
*/

View file

@ -1,8 +1,15 @@
package com.fsck.k9.mail
import com.fsck.k9.mail.Message.RecipientType
import com.fsck.k9.mail.internet.*
import com.fsck.k9.mail.internet.BinaryTempFileBody
import com.fsck.k9.mail.internet.BinaryTempFileMessageBody
import com.fsck.k9.mail.internet.CharsetSupport
import com.fsck.k9.mail.internet.MimeBodyPart
import com.fsck.k9.mail.internet.MimeHeader
import com.fsck.k9.mail.internet.MimeMessage
import com.fsck.k9.mail.internet.MimeMessageHelper
import com.fsck.k9.mail.internet.MimeMultipart
import com.fsck.k9.mail.internet.TextBody
import com.google.common.truth.Truth.assertThat
import okio.Buffer
import org.apache.james.mime4j.util.MimeUtil
@ -11,7 +18,8 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RuntimeEnvironment
import java.io.ByteArrayOutputStream
import java.util.*
import java.util.Date
import java.util.TimeZone
@RunWith(K9LibRobolectricTestRunner::class)
@ -259,7 +267,7 @@ class MessageTest {
private fun sampleMessage(): MimeMessage {
val message = MimeMessage().apply {
setFrom(Address("from@example.com"))
setRecipient(RecipientType.TO, Address("to@example.com"))
setHeader("To", "to@example.com")
subject = "Test Message"
setHeader("Date", "Wed, 28 Aug 2013 08:51:09 -0400")
setEncoding(MimeUtil.ENC_7BIT)

View file

@ -0,0 +1,60 @@
package com.fsck.k9.mail.internet
import com.fsck.k9.mail.Address
import com.fsck.k9.mail.K9LibRobolectricTestRunner
import com.fsck.k9.mail.crlf
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(K9LibRobolectricTestRunner::class)
class AddressHeaderBuilderTest {
@Test
fun createHeaderValue_withSingleAddress() {
val addresses = arrayOf(Address("test@domain.example"))
val headerValue = AddressHeaderBuilder.createHeaderValue(addresses)
assertEquals("test@domain.example", headerValue)
}
@Test
fun createHeaderValue_withTwoAddressesThatFitOnSingleLine() {
val addresses = arrayOf(
Address("one@domain.example"),
Address("two@domain.example")
)
val headerValue = AddressHeaderBuilder.createHeaderValue(addresses)
assertEquals("one@domain.example, two@domain.example", headerValue)
}
@Test
fun createHeaderValue_withMultipleAddressesThatNeedWrapping() {
val addresses = arrayOf(
Address("one@domain.example", "Person One"),
Address("two+because.i.can@this.is.quite.some.domain.example", "Person \"Long Email Address\" Two"),
Address("three@domain.example", "Person Three"),
Address("four@domain.example", "Person Four"),
Address("five@domain.example", "Person Five")
)
val headerValue = AddressHeaderBuilder.createHeaderValue(addresses)
assertEquals("""
|Person One <one@domain.example>,
| "Person \"Long Email Address\" Two" <two+because.i.can@this.is.quite.some.domain.example>,
| Person Three <three@domain.example>, Person Four <four@domain.example>,
| Person Five <five@domain.example>
""".trimMargin().crlf(), headerValue)
}
@Test(expected = IllegalArgumentException::class)
fun createHeaderValue_withoutAddresses_shouldThrow() {
AddressHeaderBuilder.createHeaderValue(emptyArray())
}
}

View file

@ -22,7 +22,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.Queue;
import androidx.annotation.VisibleForTesting;
import android.text.TextUtils;
import com.fsck.k9.mail.Address;
@ -379,7 +378,7 @@ public class SmtpTransport extends Transport {
addresses.addAll(Arrays.asList(message.getRecipients(RecipientType.CC)));
addresses.addAll(Arrays.asList(message.getRecipients(RecipientType.BCC)));
}
message.setRecipients(RecipientType.BCC, null);
message.removeHeader("Bcc");
Map<String, List<String>> charsetAddressesMap = new HashMap<>();
for (Address address : addresses) {