Add support for UTF-8 data in BODYSTRUCTURE response

This commit is contained in:
cketti 2022-09-14 15:30:50 +02:00
parent dd3d103fd3
commit 037afd0596
4 changed files with 40 additions and 6 deletions

View file

@ -26,6 +26,7 @@ internal const val SEMICOLON = ';'
internal const val EQUALS_SIGN = '='
internal const val ASTERISK = '*'
internal const val SINGLE_QUOTE = '\''
internal const val BACKSLASH = '\\'
internal fun Char.isTSpecial() = this in TSPECIALS

View file

@ -145,7 +145,7 @@ object MimeParameterEncoder {
private fun String.isQuotable() = all { it.isQuotable() }
fun String.quoted(): String {
private fun String.quoted(): String {
// quoted-string = [CFWS] DQUOTE *([FWS] qcontent) [FWS] DQUOTE [CFWS]
// qcontent = qtext / quoted-pair
// quoted-pair = ("\" (VCHAR / WSP))
@ -165,6 +165,22 @@ object MimeParameterEncoder {
}
}
// RFC 6532-style header values
// Right now we only create such values for internal use (see IMAP BODYSTRUCTURE response parsing code)
fun String.quotedUtf8(): String {
return buildString(capacity = length + 16) {
append(DQUOTE)
for (c in this@quotedUtf8) {
if (c == DQUOTE || c == BACKSLASH) {
append('\\').append(c)
} else {
append(c)
}
}
append(DQUOTE)
}
}
private fun String.quotedLength(): Int {
var length = 2 /* start and end quote */
for (c in this) {

View file

@ -16,14 +16,12 @@ import com.fsck.k9.mail.internet.MimeHeader
import com.fsck.k9.mail.internet.MimeMessageHelper
import com.fsck.k9.mail.internet.MimeMultipart
import com.fsck.k9.mail.internet.MimeParameterEncoder.isToken
import com.fsck.k9.mail.internet.MimeParameterEncoder.quoted
import com.fsck.k9.mail.internet.MimeParameterEncoder.quotedUtf8
import com.fsck.k9.mail.internet.MimeUtility
import java.io.IOException
import java.io.InputStream
import java.text.SimpleDateFormat
import java.util.Date
import java.util.HashMap
import java.util.LinkedHashSet
import java.util.Locale
import kotlin.math.max
import kotlin.math.min
@ -891,7 +889,7 @@ internal class RealImapFolder(
for (i in bodyParams.indices step 2) {
val paramName = bodyParams.getString(i)
val paramValue = bodyParams.getString(i + 1)
val encodedValue = if (paramValue.isToken()) paramValue else paramValue.quoted()
val encodedValue = if (paramValue.isToken()) paramValue else paramValue.quotedUtf8()
contentType.append(String.format(";\r\n %s=%s", paramName, encodedValue))
}
}
@ -918,7 +916,7 @@ internal class RealImapFolder(
for (i in bodyDispositionParams.indices step 2) {
val paramName = bodyDispositionParams.getString(i).lowercase()
val paramValue = bodyDispositionParams.getString(i + 1)
val encodedValue = if (paramValue.isToken()) paramValue else paramValue.quoted()
val encodedValue = if (paramValue.isToken()) paramValue else paramValue.quotedUtf8()
contentDisposition.append(String.format(";\r\n %s=%s", paramName, encodedValue))
}
}

View file

@ -770,6 +770,15 @@ class RealImapFolderTest {
)
}
@Test
fun `fetch() with UTF-8 encoded content type parameter`() {
testHeaderFromBodyStructure(
bodyStructure = """("text" "plain" ("name" "filenäme.ext") NIL NIL "7bit" 42 23)""",
headerName = MimeHeader.HEADER_CONTENT_TYPE,
expectedHeaderValue = "text/plain;\r\n name=\"filenäme.ext\""
)
}
@Test
fun `fetch() with simple content disposition parameter`() {
testHeaderFromBodyStructure(
@ -810,6 +819,16 @@ class RealImapFolderTest {
)
}
@Test
fun `fetch() with UTF-8 encoded content disposition parameter`() {
testHeaderFromBodyStructure(
bodyStructure = """("application" "octet-stream" NIL NIL NIL "8bit" 23 NIL """ +
"""("attachment" ("filename" "filenäme.ext")) NIL NIL)""",
headerName = MimeHeader.HEADER_CONTENT_DISPOSITION,
expectedHeaderValue = "attachment;\r\n filename=\"filenäme.ext\";\r\n size=23"
)
}
@Test
fun fetch_withBodySaneFetchProfile_shouldIssueRespectiveCommand() {
val folder = createFolder("Folder")