IMAP: Fix creating header values from a BODYSTRUCTURE item
This commit is contained in:
parent
b7435fa2ef
commit
d82b462565
3 changed files with 101 additions and 5 deletions
|
@ -138,14 +138,14 @@ object MimeParameterEncoder {
|
|||
return length
|
||||
}
|
||||
|
||||
private fun String.isToken() = when {
|
||||
fun String.isToken() = when {
|
||||
isEmpty() -> false
|
||||
else -> all { it.isTokenChar() }
|
||||
}
|
||||
|
||||
private fun String.isQuotable() = all { it.isQuotable() }
|
||||
|
||||
private fun String.quoted(): String {
|
||||
fun String.quoted(): String {
|
||||
// quoted-string = [CFWS] DQUOTE *([FWS] qcontent) [FWS] DQUOTE [CFWS]
|
||||
// qcontent = qtext / quoted-pair
|
||||
// quoted-pair = ("\" (VCHAR / WSP))
|
||||
|
|
|
@ -15,6 +15,8 @@ import com.fsck.k9.mail.internet.MimeBodyPart
|
|||
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.MimeUtility
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
@ -889,7 +891,8 @@ internal class RealImapFolder(
|
|||
for (i in bodyParams.indices step 2) {
|
||||
val paramName = bodyParams.getString(i)
|
||||
val paramValue = bodyParams.getString(i + 1)
|
||||
contentType.append(String.format(";\r\n %s=\"%s\"", paramName, paramValue))
|
||||
val encodedValue = if (paramValue.isToken()) paramValue else paramValue.quoted()
|
||||
contentType.append(String.format(";\r\n %s=%s", paramName, encodedValue))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -915,7 +918,8 @@ internal class RealImapFolder(
|
|||
for (i in bodyDispositionParams.indices step 2) {
|
||||
val paramName = bodyDispositionParams.getString(i).lowercase()
|
||||
val paramValue = bodyDispositionParams.getString(i + 1)
|
||||
contentDisposition.append(String.format(";\r\n %s=\"%s\"", paramName, paramValue))
|
||||
val encodedValue = if (paramValue.isToken()) paramValue else paramValue.quoted()
|
||||
contentDisposition.append(String.format(";\r\n %s=%s", paramName, encodedValue))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -731,7 +731,83 @@ class RealImapFolderTest {
|
|||
|
||||
folder.fetch(messages, fetchProfile, null, MAX_DOWNLOAD_SIZE)
|
||||
|
||||
verify(messages[0]).setHeader(MimeHeader.HEADER_CONTENT_TYPE, "text/plain;\r\n CHARSET=\"US-ASCII\"")
|
||||
verify(messages[0]).setHeader(MimeHeader.HEADER_CONTENT_TYPE, "text/plain;\r\n CHARSET=US-ASCII")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch() with simple content type parameter`() {
|
||||
testHeaderFromBodyStructure(
|
||||
bodyStructure = """("text" "plain" ("name" "token") NIL NIL "7bit" 42 23)""",
|
||||
headerName = MimeHeader.HEADER_CONTENT_TYPE,
|
||||
expectedHeaderValue = "text/plain;\r\n name=token"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch() with content type parameter that needs to be a quoted string`() {
|
||||
testHeaderFromBodyStructure(
|
||||
bodyStructure = """("text" "plain" ("name" "one two three") NIL NIL "7bit" 42 23)""",
|
||||
headerName = MimeHeader.HEADER_CONTENT_TYPE,
|
||||
expectedHeaderValue = "text/plain;\r\n name=\"one two three\""
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch() with content type parameter that needs to be a quoted string with escaped characters`() {
|
||||
testHeaderFromBodyStructure(
|
||||
bodyStructure = """("text" "plain" ("name" "one \"two\" three") NIL NIL "7bit" 42 23)""",
|
||||
headerName = MimeHeader.HEADER_CONTENT_TYPE,
|
||||
expectedHeaderValue = "text/plain;\r\n name=\"one \\\"two\\\" three\""
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch() with RFC 2231 encoded content type parameter`() {
|
||||
testHeaderFromBodyStructure(
|
||||
bodyStructure = """("text" "plain" ("name*" "utf-8''filen%C3%A4me.ext") NIL NIL "7bit" 42 23)""",
|
||||
headerName = MimeHeader.HEADER_CONTENT_TYPE,
|
||||
expectedHeaderValue = "text/plain;\r\n name*=utf-8''filen%C3%A4me.ext"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch() with simple content disposition parameter`() {
|
||||
testHeaderFromBodyStructure(
|
||||
bodyStructure = """("application" "octet-stream" NIL NIL NIL "8bit" 23 NIL """ +
|
||||
"""("attachment" ("filename" "token")) NIL NIL)""",
|
||||
headerName = MimeHeader.HEADER_CONTENT_DISPOSITION,
|
||||
expectedHeaderValue = "attachment;\r\n filename=token;\r\n size=23"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch() with content disposition parameter that needs to be a quoted string`() {
|
||||
testHeaderFromBodyStructure(
|
||||
bodyStructure = """("application" "octet-stream" NIL NIL NIL "8bit" 23 NIL """ +
|
||||
"""("attachment" ("filename" "one two three")) NIL NIL)""",
|
||||
headerName = MimeHeader.HEADER_CONTENT_DISPOSITION,
|
||||
expectedHeaderValue = "attachment;\r\n filename=\"one two three\";\r\n size=23"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch() with content disposition parameter that needs to be a quoted string with escaped characters`() {
|
||||
testHeaderFromBodyStructure(
|
||||
bodyStructure = """("application" "octet-stream" NIL NIL NIL "8bit" 23 NIL """ +
|
||||
"""("attachment" ("filename" "one \"two\" three")) NIL NIL)""",
|
||||
headerName = MimeHeader.HEADER_CONTENT_DISPOSITION,
|
||||
expectedHeaderValue = "attachment;\r\n filename=\"one \\\"two\\\" three\";\r\n size=23"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch() with RFC 2231 encoded content disposition parameter`() {
|
||||
testHeaderFromBodyStructure(
|
||||
bodyStructure = """("application" "octet-stream" NIL NIL NIL "8bit" 23 NIL """ +
|
||||
"""("attachment" ("filename*" "utf-8''filen%C3%A4me.ext")) NIL NIL)""",
|
||||
headerName = MimeHeader.HEADER_CONTENT_DISPOSITION,
|
||||
expectedHeaderValue = "attachment;\r\n filename*=utf-8''filen%C3%A4me.ext;\r\n size=23"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1118,6 +1194,22 @@ class RealImapFolderTest {
|
|||
.thenReturn(imapResponses)
|
||||
}
|
||||
|
||||
private fun testHeaderFromBodyStructure(bodyStructure: String, headerName: String, expectedHeaderValue: String) {
|
||||
val folder = createFolder("Folder")
|
||||
prepareImapFolderForOpen(OpenMode.READ_ONLY)
|
||||
folder.open(OpenMode.READ_ONLY)
|
||||
whenever(imapConnection.readResponse(anyOrNull()))
|
||||
.thenReturn(createImapResponse("* 1 FETCH (BODYSTRUCTURE $bodyStructure UID 1)"))
|
||||
.thenReturn(createImapResponse("x OK"))
|
||||
val imapMessage = ImapMessage("1")
|
||||
val messages = listOf(imapMessage)
|
||||
val fetchProfile = createFetchProfile(FetchProfile.Item.STRUCTURE)
|
||||
|
||||
folder.fetch(messages, fetchProfile, null, MAX_DOWNLOAD_SIZE)
|
||||
|
||||
assertThat(imapMessage.getHeader(headerName)).asList().containsExactly(expectedHeaderValue)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MAX_DOWNLOAD_SIZE = -1
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue