Merge pull request #5511 from k9mail/fix_identity_header
Fold identity header value so we're not generating invalid messages
This commit is contained in:
commit
97891d15e0
4 changed files with 130 additions and 7 deletions
|
@ -3,16 +3,20 @@ package com.fsck.k9.message;
|
|||
|
||||
import android.net.Uri;
|
||||
import android.net.Uri.Builder;
|
||||
import timber.log.Timber;
|
||||
|
||||
import com.fsck.k9.Account.QuoteStyle;
|
||||
import com.fsck.k9.Identity;
|
||||
import com.fsck.k9.K9;
|
||||
import com.fsck.k9.controller.MessageReference;
|
||||
import com.fsck.k9.mail.internet.TextBody;
|
||||
import com.fsck.k9.message.quote.InsertableHtmlContent;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
public class IdentityHeaderBuilder {
|
||||
private static final int MAX_LINE_LENGTH = 72;
|
||||
private static final int FIRST_LINE_EXTRA_LENGTH = K9.IDENTITY_HEADER.length() + 2;
|
||||
|
||||
private InsertableHtmlContent quotedHtmlContent;
|
||||
private QuoteStyle quoteStyle;
|
||||
private SimpleMessageFormat messageFormat;
|
||||
|
@ -95,9 +99,33 @@ public class IdentityHeaderBuilder {
|
|||
appendValue(IdentityField.QUOTED_TEXT_MODE, quotedTextMode);
|
||||
|
||||
String k9identity = IdentityField.IDENTITY_VERSION_1 + uri.build().getEncodedQuery();
|
||||
String headerValue = foldHeaderValue(k9identity);
|
||||
|
||||
Timber.d("Generated identity: %s", k9identity);
|
||||
return k9identity;
|
||||
Timber.d("Generated identity: %s", headerValue);
|
||||
return headerValue;
|
||||
}
|
||||
|
||||
private String foldHeaderValue(String input) {
|
||||
int inputLength = input.length();
|
||||
int endOfFirstLine = MAX_LINE_LENGTH - FIRST_LINE_EXTRA_LENGTH;
|
||||
if (inputLength <= endOfFirstLine) {
|
||||
return input;
|
||||
}
|
||||
|
||||
int extraLines = (inputLength - endOfFirstLine - 1) / (MAX_LINE_LENGTH - 1) + 1;
|
||||
int builderSize = inputLength + extraLines * 3 /* CR LF SPACE */;
|
||||
StringBuilder headerValue = new StringBuilder(builderSize);
|
||||
|
||||
headerValue.append(input, 0, endOfFirstLine);
|
||||
int start = endOfFirstLine;
|
||||
while (start < inputLength) {
|
||||
headerValue.append("\r\n ");
|
||||
int end = start + Math.min(MAX_LINE_LENGTH - 1, inputLength - start);
|
||||
headerValue.append(input, start, end);
|
||||
start = end;
|
||||
}
|
||||
|
||||
return headerValue.toString();
|
||||
}
|
||||
|
||||
private void appendValue(IdentityField field, int value) {
|
||||
|
|
|
@ -29,10 +29,12 @@ public class IdentityHeaderParser {
|
|||
return identity;
|
||||
}
|
||||
|
||||
String encodedString = unfoldHeaderValue(identityString);
|
||||
|
||||
// Check to see if this is a "next gen" identity.
|
||||
if (identityString.charAt(0) == IdentityField.IDENTITY_VERSION_1.charAt(0) && identityString.length() > 2) {
|
||||
if (encodedString.charAt(0) == IdentityField.IDENTITY_VERSION_1.charAt(0) && encodedString.length() > 2) {
|
||||
Uri.Builder builder = new Uri.Builder();
|
||||
builder.encodedQuery(identityString.substring(1)); // Need to cut off the ! at the beginning.
|
||||
builder.encodedQuery(encodedString.substring(1)); // Need to cut off the ! at the beginning.
|
||||
Uri uri = builder.build();
|
||||
for (IdentityField key : IdentityField.values()) {
|
||||
String value = uri.getQueryParameter(key.value());
|
||||
|
@ -56,9 +58,9 @@ public class IdentityHeaderParser {
|
|||
} else {
|
||||
// Legacy identity
|
||||
|
||||
Timber.d("Got a saved legacy identity: %s", identityString);
|
||||
Timber.d("Got a saved legacy identity: %s", encodedString);
|
||||
|
||||
StringTokenizer tokenizer = new StringTokenizer(identityString, ":", false);
|
||||
StringTokenizer tokenizer = new StringTokenizer(encodedString, ":", false);
|
||||
|
||||
// First item is the body length. We use this to separate the composed reply from the quoted text.
|
||||
if (tokenizer.hasMoreTokens()) {
|
||||
|
@ -85,4 +87,8 @@ public class IdentityHeaderParser {
|
|||
|
||||
return identity;
|
||||
}
|
||||
|
||||
private static String unfoldHeaderValue(String identityString) {
|
||||
return identityString.replaceAll("\r?\n ", "");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
package com.fsck.k9.message
|
||||
|
||||
import com.fsck.k9.Account.QuoteStyle
|
||||
import com.fsck.k9.Identity
|
||||
import com.fsck.k9.RobolectricTest
|
||||
import com.fsck.k9.mail.internet.MimeHeaderChecker
|
||||
import com.fsck.k9.mail.internet.TextBody
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
|
||||
private const val IDENTITY_HEADER = "X-K9mail-Identity"
|
||||
|
||||
class IdentityHeaderBuilderTest : RobolectricTest() {
|
||||
@Test
|
||||
fun `valid unstructured header field value`() {
|
||||
val signature = "a".repeat(1000)
|
||||
|
||||
val identityHeader = IdentityHeaderBuilder()
|
||||
.setCursorPosition(0)
|
||||
.setIdentity(createIdentity(signatureUse = true))
|
||||
.setIdentityChanged(false)
|
||||
.setMessageFormat(SimpleMessageFormat.TEXT)
|
||||
.setMessageReference(null)
|
||||
.setQuotedHtmlContent(null)
|
||||
.setQuoteStyle(QuoteStyle.PREFIX)
|
||||
.setQuoteTextMode(QuotedTextMode.NONE)
|
||||
.setSignature(signature)
|
||||
.setSignatureChanged(true)
|
||||
.setBody(TextBody("irrelevant"))
|
||||
.setBodyPlain(null)
|
||||
.build()
|
||||
|
||||
assertThat(identityHeader.length).isGreaterThan(1000)
|
||||
assertIsValidHeader(identityHeader)
|
||||
}
|
||||
|
||||
private fun assertIsValidHeader(identityHeader: String) {
|
||||
try {
|
||||
MimeHeaderChecker.checkHeader(IDENTITY_HEADER, identityHeader)
|
||||
} catch (e: Exception) {
|
||||
println("$IDENTITY_HEADER: $identityHeader")
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
private fun createIdentity(
|
||||
description: String? = null,
|
||||
name: String? = null,
|
||||
email: String? = null,
|
||||
signature: String? = null,
|
||||
signatureUse: Boolean = false,
|
||||
replyTo: String? = null
|
||||
): Identity {
|
||||
return Identity(description, name, email, signature, signatureUse, replyTo)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package com.fsck.k9.message
|
||||
|
||||
import com.fsck.k9.RobolectricTest
|
||||
import com.fsck.k9.helper.toCrLf
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
|
||||
class IdentityHeaderParserTest : RobolectricTest() {
|
||||
@Test
|
||||
fun `folded header value`() {
|
||||
val input = """
|
||||
|!l=10&o=0&qs=PREFIX&f=TEXT&s=aaaaaaaaaaaaaaaaaaaaaaaa
|
||||
| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&p=0&q=NONE
|
||||
""".trimMargin().toCrLf()
|
||||
|
||||
val result = IdentityHeaderParser.parse(input)
|
||||
|
||||
assertThat(result).containsEntry(IdentityField.SIGNATURE, "a".repeat(1000))
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue