Merge pull request #3912 from k9mail/fix_recipient_headers
Fold recipient headers
This commit is contained in:
commit
e65daf5c08
14 changed files with 162 additions and 115 deletions
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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" +
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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('(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=')
|
||||
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue