Merge pull request #6030 from k9mail/fix_format_flowed

Fix bug in `FlowedMessageUtils.deflow()`
This commit is contained in:
cketti 2022-04-22 16:27:28 +02:00 committed by GitHub
commit 77227b3290
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 239 additions and 19 deletions

View file

@ -45,39 +45,54 @@ public final class FlowedMessageUtils {
String line = i < lines.length ? lines[i] : null;
int actualQuoteDepth = 0;
if (line != null && line.length() > 0) {
if (line.equals(RFC2646_SIGNATURE))
if (line != null) {
if (line.equals(RFC2646_SIGNATURE)) {
// signature handling (the previous line is not flowed)
resultLineFlowed = false;
else if (line.charAt(0) == RFC2646_QUOTE) {
} else if (line.length() > 0 && line.charAt(0) == RFC2646_QUOTE) {
// Quote
actualQuoteDepth = 1;
while (actualQuoteDepth < line.length() && line.charAt(actualQuoteDepth) == RFC2646_QUOTE) actualQuoteDepth ++;
while (actualQuoteDepth < line.length() && line.charAt(actualQuoteDepth) == RFC2646_QUOTE) {
actualQuoteDepth++;
}
// if quote-depth changes wrt the previous line then this is not flowed
if (resultLineQuoteDepth != actualQuoteDepth) resultLineFlowed = false;
if (resultLineQuoteDepth != actualQuoteDepth) {
resultLineFlowed = false;
}
line = line.substring(actualQuoteDepth);
} else {
// id quote-depth changes wrt the first line then this is not flowed
if (resultLineQuoteDepth > 0) resultLineFlowed = false;
// if quote-depth changes wrt the first line then this is not flowed
if (resultLineQuoteDepth > 0) {
resultLineFlowed = false;
}
}
if (line.length() > 0 && line.charAt(0) == RFC2646_SPACE)
if (line.length() > 0 && line.charAt(0) == RFC2646_SPACE) {
// Line space-stuffed
line = line.substring(1);
}
// if the previous was the last then it was not flowed
} else if (line == null) resultLineFlowed = false;
} else {
resultLineFlowed = false;
}
// Add the PREVIOUS line.
// This often will find the flow looking for a space as the last char of the line.
// With quote changes or signatures it could be the followinf line to void the flow.
// With quote changes or signatures it could be the following line to void the flow.
if (!resultLineFlowed && i > 0) {
if (resultLineQuoteDepth > 0) resultLine.insert(0, RFC2646_SPACE);
for (int j = 0; j < resultLineQuoteDepth; j++) resultLine.insert(0, RFC2646_QUOTE);
if (result == null) result = new StringBuffer();
else result.append(RFC2646_CRLF);
if (resultLineQuoteDepth > 0) {
resultLine.insert(0, RFC2646_SPACE);
}
for (int j = 0; j < resultLineQuoteDepth; j++) {
resultLine.insert(0, RFC2646_QUOTE);
}
if (result == null) {
result = new StringBuffer();
} else {
result.append(RFC2646_CRLF);
}
result.append(resultLine.toString());
resultLine = new StringBuffer();
resultLineFlowed = false;
@ -86,13 +101,16 @@ public final class FlowedMessageUtils {
if (line != null) {
if (!line.equals(RFC2646_SIGNATURE) && line.endsWith("" + RFC2646_SPACE) && i < lines.length - 1) {
// Line flowed (NOTE: for the split operation the line having i == lines.length is the last that does not end with RFC2646_CRLF)
if (delSp) line = line.substring(0, line.length() - 1);
// Line flowed (NOTE: for the split operation the line having i == lines.length is the last that
// does not end with RFC2646_CRLF)
if (delSp) {
line = line.substring(0, line.length() - 1);
}
resultLineFlowed = true;
} else {
resultLineFlowed = false;
}
else resultLineFlowed = false;
resultLine.append(line);
}
}

View file

@ -0,0 +1,202 @@
package com.fsck.k9.mail.internet
import com.fsck.k9.mail.crlf
import com.google.common.truth.Truth.assertThat
import org.junit.Test
private const val DEL_SP_NO = false
private const val DEL_SP_YES = true
class FlowedMessageUtilsTest {
@Test
fun `deflow() with simple text`() {
val input = "Text that should be \r\n" +
"displayed on one line"
val result = FlowedMessageUtils.deflow(input, DEL_SP_NO)
assertThat(result).isEqualTo("Text that should be displayed on one line")
}
@Test
fun `deflow() with only some lines ending in space`() {
val input = "Text that \r\n" +
"should be \r\n" +
"displayed on \r\n" +
"one line.\r\n" +
"Text that should retain\r\n" +
"its line break."
val result = FlowedMessageUtils.deflow(input, DEL_SP_NO)
assertThat(result).isEqualTo(
"""
Text that should be displayed on one line.
Text that should retain
its line break.
""".trimIndent().crlf()
)
}
@Test
fun `deflow() with nothing to do`() {
val input = "Line one\r\nLine two\r\n"
val result = FlowedMessageUtils.deflow(input, DEL_SP_NO)
assertThat(result).isEqualTo(input)
}
@Test
fun `deflow() with quoted text`() {
val input = "On [date], [user] wrote:\r\n" +
"> Text that should be displayed \r\n" +
"> on one line\r\n" +
"\r\n" +
"Some more text that should be \r\n" +
"displayed on one line.\r\n"
val result = FlowedMessageUtils.deflow(input, DEL_SP_NO)
assertThat(result).isEqualTo(
"""
|On [date], [user] wrote:
|> Text that should be displayed on one line
|
|Some more text that should be displayed on one line.
|
""".trimMargin().crlf()
)
}
@Test
fun `deflow() with quoted text ending in space`() {
val input = "> Quoted text \r\n" +
"Some other text"
val result = FlowedMessageUtils.deflow(input, DEL_SP_NO)
assertThat(result).isEqualTo("> Quoted text \r\nSome other text")
}
@Test
fun `deflow() with quoted text ending in space before quoted text of different quoting depth`() {
val input = ">> Depth 2 \r\n" +
"> Depth 1 \r\n" +
"> is here\r\n" +
"Some other text"
val result = FlowedMessageUtils.deflow(input, DEL_SP_NO)
assertThat(result).isEqualTo(
"""
>> Depth 2${" "}
> Depth 1 is here
Some other text
""".trimIndent().crlf()
)
}
@Test
fun `deflow() with quoted text ending in space followed by empty line`() {
val input = "> Quoted \r\n" +
"\r\n" +
"Text"
val result = FlowedMessageUtils.deflow(input, DEL_SP_NO)
assertThat(result).isEqualTo(input)
}
@Test
fun `deflow() with delSp=true`() {
val input = "Text that is wrapped mid wo \r\nrd"
val result = FlowedMessageUtils.deflow(input, DEL_SP_YES)
assertThat(result).isEqualTo("Text that is wrapped mid word")
}
@Test
fun `deflow() with quoted text and space-stuffing and delSp=true`() {
val input = "> Quoted te \r\n" +
"> xt"
val result = FlowedMessageUtils.deflow(input, DEL_SP_YES)
assertThat(result).isEqualTo("> Quoted text")
}
@Test
fun `deflow() with space-stuffed second line`() {
val input = "Text that should be \r\n" +
" displayed on one line"
val result = FlowedMessageUtils.deflow(input, DEL_SP_NO)
assertThat(result).isEqualTo("Text that should be displayed on one line")
}
@Test
fun `deflow() with only space-stuffing`() {
val input = "Line 1\r\n" +
" Line 2\r\n" +
" Line 3\r\n"
val result = FlowedMessageUtils.deflow(input, DEL_SP_NO)
assertThat(result).isEqualTo("Line 1\r\nLine 2\r\nLine 3\r\n")
}
@Test
fun `deflow() with quoted space-stuffed second line`() {
val input = "> Text that should be \r\n" +
"> displayed on one line"
val result = FlowedMessageUtils.deflow(input, DEL_SP_NO)
assertThat(result).isEqualTo("> Text that should be displayed on one line")
}
@Test
fun `deflow() with text containing signature`() {
val input = "Text that should be \r\n" +
"displayed on one line.\r\n" +
"\r\n" +
"-- \r\n" +
"Signature \r\n" +
"text"
val result = FlowedMessageUtils.deflow(input, DEL_SP_NO)
assertThat(result).isEqualTo(
"""
Text that should be displayed on one line.
--${" "}
Signature text
""".trimIndent().crlf()
)
}
@Test
fun `deflow() with quoted text containing signature`() {
val input = "> Text that should be \r\n" +
"> displayed on one line.\r\n" +
"> \r\n" +
"> -- \r\n" +
"> Signature \r\n" +
"> text"
val result = FlowedMessageUtils.deflow(input, DEL_SP_NO)
assertThat(result).isEqualTo(
"""
> Text that should be displayed on one line.
>${" "}
> --${" "}
> Signature text
""".trimIndent().crlf()
)
}
}