diff --git a/mail/common/src/main/java/com/fsck/k9/mail/internet/FlowedMessageUtils.java b/mail/common/src/main/java/com/fsck/k9/mail/internet/FlowedMessageUtils.java index 25a10847b..580a5acaa 100644 --- a/mail/common/src/main/java/com/fsck/k9/mail/internet/FlowedMessageUtils.java +++ b/mail/common/src/main/java/com/fsck/k9/mail/internet/FlowedMessageUtils.java @@ -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); } } diff --git a/mail/common/src/test/java/com/fsck/k9/mail/internet/FlowedMessageUtilsTest.kt b/mail/common/src/test/java/com/fsck/k9/mail/internet/FlowedMessageUtilsTest.kt new file mode 100644 index 000000000..cf70f7145 --- /dev/null +++ b/mail/common/src/test/java/com/fsck/k9/mail/internet/FlowedMessageUtilsTest.kt @@ -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() + ) + } +}