Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions legacy/core/src/main/java/com/fsck/k9/helper/MimeTypeUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,26 @@ public static String getMimeTypeByExtension(String filename) {
return DEFAULT_ATTACHMENT_MIME_TYPE;
}

/**
* Returns a MIME type that is consistent with the extension of {@code displayName}.
*
* <p>This is used when creating a document via the Storage Access Framework. If the declared MIME type does not
* round-trip to the display name's extension, the provider appends the declared type's preferred extension (e.g.
* saving {@code appointment.ics} declared as {@code text/plain} would produce {@code appointment.ics.txt}). By
* passing a MIME type derived from the extension, the provider keeps the filename unchanged.
*
* <p>When the display name has no extension, there is nothing to preserve, so the declared MIME type (or
* {@link #DEFAULT_ATTACHMENT_MIME_TYPE} when it is {@code null}) is returned and the provider may add a suitable
* extension.
*/
public static String getMimeTypeForFilename(String displayName, String declaredMimeType) {
if (displayName != null && displayName.lastIndexOf('.') != -1) {
return getMimeTypeByExtension(displayName);
}

return declaredMimeType != null ? declaredMimeType : DEFAULT_ATTACHMENT_MIME_TYPE;
}

public static String getExtensionByMimeType(@NotNull String mimeType) {
String lowerCaseMimeType = mimeType.toLowerCase(Locale.US);
for (String[] contentTypeMapEntry : MIME_TYPE_BY_EXTENSION_MAP) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.fsck.k9.helper

import assertk.assertThat
import assertk.assertions.isEqualTo
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class MimeTypeUtilTest {

@Test
fun `getMimeTypeForFilename returns extension-consistent type for a known extension`() {
val result = MimeTypeUtil.getMimeTypeForFilename("appointment.ics", "text/plain")

assertThat(result).isEqualTo("text/calendar")
}

@Test
fun `getMimeTypeForFilename returns extension-consistent type for a pkpass file`() {
val result = MimeTypeUtil.getMimeTypeForFilename("boardingpass.pkpass", "application/octet-stream")

assertThat(result).isEqualTo("application/vnd.apple.pkpass")
}

@Test
fun `getMimeTypeForFilename ignores the declared type when the extension is known`() {
val result = MimeTypeUtil.getMimeTypeForFilename("notes.txt", "application/octet-stream")

assertThat(result).isEqualTo("text/plain")
}

@Test
fun `getMimeTypeForFilename falls back to declared type when there is no extension`() {
val result = MimeTypeUtil.getMimeTypeForFilename("noname", "text/plain")

assertThat(result).isEqualTo("text/plain")
}

@Test
fun `getMimeTypeForFilename falls back to default type when there is no extension and no declared type`() {
val result = MimeTypeUtil.getMimeTypeForFilename("noname", null)

assertThat(result).isEqualTo("application/octet-stream")
}

@Test
fun `getMimeTypeForFilename resolves an unknown extension to the default type`() {
val result = MimeTypeUtil.getMimeTypeForFilename("mystery.unknownext", "text/plain")

assertThat(result).isEqualTo("application/octet-stream")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import com.fsck.k9.fragment.ConfirmationDialogFragment
import com.fsck.k9.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener
import com.fsck.k9.helper.HttpsUnsubscribeUri
import com.fsck.k9.helper.MailtoUnsubscribeUri
import com.fsck.k9.helper.MimeTypeUtil
import com.fsck.k9.helper.UnsubscribeUri
import com.fsck.k9.mail.Part
import com.fsck.k9.mailstore.AttachmentViewInfo
Expand Down Expand Up @@ -1197,9 +1198,7 @@ class MessageViewFragment :
createDocumentLauncher.launch(
input = CreateDocumentResultContract.Input(
title = attachment.displayName ?: getString(MessageReaderR.string.unnamed_attachment_title),
mimeType = requireNotNull(attachment.mimeType) {
"Invalid attachment type. The mimeType is null. Attachment = $attachment"
},
mimeType = MimeTypeUtil.getMimeTypeForFilename(attachment.displayName, attachment.mimeType),
),
)
} catch (_: ActivityNotFoundException) {
Expand Down
Loading