Skip to content

Commit

Permalink
Add header folding to MimeHeaderEncoder
Browse files Browse the repository at this point in the history
  • Loading branch information
cketti committed Mar 6, 2024
1 parent 62caaba commit 09a155b
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 6 deletions.
@@ -1,23 +1,29 @@
package com.fsck.k9.mail.internet

import org.apache.james.mime4j.util.MimeUtil

object MimeHeaderEncoder {
@JvmStatic
fun encode(name: String, value: String): String {
// TODO: Fold long text that provides enough opportunities for folding and doesn't contain any characters that
// need to be encoded.
return if (hasToBeEncoded(name, value)) {
EncoderUtil.encodeEncodedWord(value)

// Number of characters already used up on the first line (header field name + colon + space)
val usedCharacters = name.length + COLON_PLUS_SPACE_LENGTH

return if (hasToBeEncoded(value, usedCharacters)) {
MimeUtil.fold(EncoderUtil.encodeEncodedWord(value), usedCharacters)
} else {
value
}
}

private fun hasToBeEncoded(name: String, value: String): Boolean {
return exceedsRecommendedLineLength(name, value) || charactersNeedEncoding(value)
private fun hasToBeEncoded(value: String, usedCharacters: Int): Boolean {
return exceedsRecommendedLineLength(value, usedCharacters) || charactersNeedEncoding(value)
}

private fun exceedsRecommendedLineLength(name: String, value: String): Boolean {
return name.length + COLON_PLUS_SPACE_LENGTH + value.length > RECOMMENDED_MAX_LINE_LENGTH
private fun exceedsRecommendedLineLength(value: String, usedCharacters: Int): Boolean {
return usedCharacters + value.length > RECOMMENDED_MAX_LINE_LENGTH
}

private fun charactersNeedEncoding(text: String): Boolean {
Expand Down
@@ -0,0 +1,75 @@
package com.fsck.k9.mail.internet

import assertk.Assert
import assertk.all
import assertk.assertThat
import assertk.assertions.each
import assertk.assertions.isEqualTo
import assertk.assertions.isLessThanOrEqualTo
import assertk.assertions.length
import assertk.fail
import kotlin.test.Test

class MimeHeaderEncoderTest {
@Test
fun `short subject containing only ASCII characters should not be encoded`() {
val result = MimeHeaderEncoder.encode(name = "Subject", value = "Hello World!")

assertThat(result).isEqualTo("Hello World!")
}

@Test
fun `short subject containing non-ASCII characters should be encoded`() {
val result = MimeHeaderEncoder.encode(name = "Subject", value = "Gänseblümchen")

assertThat(result).isEqualTo("=?UTF-8?Q?G=C3=A4nsebl=C3=BCmchen?=")
}

@Test
fun `subject with recommended line length should not be folded`() {
val subject = "a".repeat(RECOMMENDED_MAX_LINE_LENGTH - "Subject: ".length)

val result = MimeHeaderEncoder.encode(name = "Subject", value = subject)

assertThat(result).isEqualTo(subject)
}

@Test
fun `subject exceeding recommended line length should be folded`() {
val subject = "a".repeat(34) + " " + "a".repeat(35)

val result = MimeHeaderEncoder.encode(name = "Subject", value = subject)

assertThat(result).all {
transform { it.lines() }.each {
it.length().isLessThanOrEqualTo(RECOMMENDED_MAX_LINE_LENGTH)
}
isValidHeader(name = "Subject")
decodesTo(subject)
}
}

@Test
fun `subject exceeding maximum line length should be encoded`() {
val subject = "a".repeat(999)

val result = MimeHeaderEncoder.encode(name = "Subject", value = subject)

assertThat(result).all {
isValidHeader(name = "Subject")
decodesTo(subject)
}
}

private fun Assert<String>.isValidHeader(name: String) = given { value ->
try {
MimeHeaderChecker.checkHeader(name, value)
} catch (e: MimeHeaderParserException) {
fail(AssertionError("Not a valid RFC5322 header", e))
}
}

private fun Assert<String>.decodesTo(expected: String) = given { value ->
assertThat(MimeUtility.unfoldAndDecode(value)).isEqualTo(expected)
}
}

0 comments on commit 09a155b

Please sign in to comment.