Firefox · Core
CVE-2026-100823
Logic Error in Core
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifmobile/android/android-components/components/support/ktx/src/main/java/mozilla/components/support/ktx/kotlin/String.kt |
modified |
Files Changed
mobile/android/android-components/components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadNotification.ktmobile/android/android-components/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadNotificationTest.ktmobile/android/android-components/components/support/ktx/src/main/java/mozilla/components/support/ktx/kotlin/String.ktmobile/android/android-components/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlin/StringTest.kt
Patch
diff --git a/mobile/android/android-components/components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadNotification.kt b/mobile/android/android-components/components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadNotification.kt
index 75bffa56afa..1cd7cdf6769 100644
--- a/mobile/android/android-components/components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadNotification.kt
+++ b/mobile/android/android-components/components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadNotification.kt
@@ -30,6 +30,7 @@ import mozilla.components.feature.downloads.AbstractFetchDownloadService.Compani
import mozilla.components.feature.downloads.AbstractFetchDownloadService.Companion.ACTION_RESUME
import mozilla.components.feature.downloads.AbstractFetchDownloadService.Companion.ACTION_TRY_AGAIN
import mozilla.components.feature.downloads.AbstractFetchDownloadService.DownloadJobState
+import mozilla.components.support.ktx.kotlin.truncateMiddle
import mozilla.components.support.utils.DownloadFileUtils
@Suppress("LargeClass")
@@ -40,6 +41,7 @@ internal object DownloadNotification {
internal const val NOTIFICATION_DOWNLOAD_GROUP_ID = 100
private const val LEGACY_NOTIFICATION_CHANNEL_ID = "Downloads"
internal const val PERCENTAGE_MULTIPLIER = 100
+ private const val MAX_FILENAME_LENGTH = 25
@VisibleForTesting
internal fun createDownloadGroupNotification(
@@ -89,11 +91,12 @@ internal object DownloadNotification {
val channelId = ensureChannelExists(context)
val isIndeterminate = downloadState.isIndeterminate()
val percentCopied = downloadState.getPercent() ?: -1
+ val fileName = downloadState.fileName?.truncateMiddle(MAX_FILENAME_LENGTH).orEmpty()
return NotificationCompat.Builder(context, channelId)
.setStyle(
NotificationCompat.BigTextStyle()
- .setBigContentTitle(downloadState.fileName.orEmpty())
+ .setBigContentTitle(fileName)
.setSummaryText(
formatDownloadTimeRemaining(
context = context,
@@ -105,7 +108,7 @@ internal object DownloadNotification {
)
)
.setSmallIcon(R.drawable.mozac_feature_download_ic_ongoing_download)
- .setContentTitle(downloadState.fileName.orEmpty())
+ .setContentTitle(fileName)
.setContentText(downloadState.getProgress(fileSizeFormatter = fileSizeFormatter))
.setColor(ContextCompat.getColor(context, notificationAccentColor))
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
@@ -128,10 +131,11 @@ internal object DownloadNotification {
notificationAccentColor: Int,
): Notification {
val channelId = ensureChannelExists(context)
+ val fileName = downloadState.fileName?.truncateMiddle(MAX_FILENAME_LENGTH).orEmpty()
return NotificationCompat.Builder(context, channelId)
.setSmallIcon(R.drawable.mozac_feature_download_ic_download)
- .setContentTitle(downloadState.fileName)
+ .setContentTitle(fileName)
.setContentText(
context.applicationContext.getString(R.string.mozac_feature_downloads_paused_notification_text)
)
@@ -157,10 +161,11 @@ internal object DownloadNotification {
contentIntent: PendingIntent = createOpenFilePendingIntent(context, downloadState, downloadFileUtils),
): Notification {
val channelId = ensureChannelExists(context)
+ val fileName = downloadState.fileName?.truncateMiddle(MAX_FILENAME_LENGTH).orEmpty()
return NotificationCompat.Builder(context, channelId)
.setSmallIcon(R.drawable.mozac_feature_download_ic_download_complete)
- .setContentTitle(downloadState.fileName)
+ .setContentTitle(fileName)
.setWhen(createdTime)
.setOnlyAlertOnce(true)
.setContentText(
@@ -183,10 +188,11 @@ internal object DownloadNotification {
notificationAccentColor: Int,
): Notification {
val channelId = ensureChannelExists(context)
+ val fileName = downloadState.fileName?.truncateMiddle(MAX_FILENAME_LENGTH).orEmpty()
return NotificationCompat.Builder(context, channelId)
.setSmallIcon(R.drawable.mozac_feature_download_ic_download_failed)
- .setContentTitle(downloadState.fileName)
+ .setContentTitle(fileName)
.setContentText(
context.applicationContext.getString(R.string.mozac_feature_downloads_failed_notification_text2)
)
@@ -209,7 +215,8 @@ internal object DownloadNotification {
notifications: List<DownloadJobState>,
): List<String> {
return notifications.take(2).map { downloadState ->
- "${downloadState.state.fileName} ${downloadState.state.getStatusDescription(
+ val fileName = downloadState.state.fileName.orEmpty().truncateMiddle(MAX_FILENAME_LENGTH)
+ "$fileName ${downloadState.state.getStatusDescription(
context = context,
fileSizeFormatter = fileSizeFormatter,
)}"
diff --git a/mobile/android/android-components/components/support/ktx/src/main/java/mozilla/components/support/ktx/kotlin/String.kt b/mobile/android/android-components/components/support/ktx/src/main/java/mozilla/components/support/ktx/kotlin/String.kt
index da838d0de4c..379ce55213d 100644
--- a/mobile/android/android-components/components/support/ktx/src/main/java/mozilla/components/support/ktx/kotlin/String.kt
+++ b/mobile/android/android-components/components/support/ktx/src/main/java/mozilla/components/support/ktx/kotlin/String.kt
@@ -480,3 +480,24 @@ internal fun String.extractBase6RawString(): String? {
contentString
}
}
+
+/**
+ * Truncates the string from the middle if it exceeds [maxLength].
+ *
+ * @param maxLength The maximum length of the string.
+ * @param ellipsis The string to use as ellipsis, defaults to "…".
+ * @return The truncated string.
+ */
+fun String.truncateMiddle(maxLength: Int, ellipsis: String = "…"): String {
+ if (length <= maxLength) {
+ return this
+ }
+ if (maxLength <= ellipsis.length) {
+ return take(maxLength)
+ }
+
+ val half = (maxLength - ellipsis.length) / 2
+ val start = take(half)
+ val end = takeLast(maxLength - ellipsis.length - half)
+ return "$start$ellipsis$end"
+}
diff --git a/mobile/android/android-components/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadNotificationTest.kt b/mobile/android/android-components/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadNotificationTest.kt
index b7f3be776de..8c5fa35727b 100644
--- a/mobile/android/android-components/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadNotificationTest.kt
+++ b/mobile/android/android-components/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadNotificationTest.kt
@@ -4,6 +4,7 @@
package mozilla.components.feature.downloads
+import android.app.Notification
import android.app.PendingIntent
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.EXTRA_PROGRESS
@@ -312,6 +313,32 @@ class DownloadNotificationTest {
assertEquals(true, notificationDownloadWithNoSize.extras.getBoolean(EXTRA_PROGRESS_INDETERMINATE))
}
+ @Test
+ fun `createOngoingDownloadNotification with long filename truncates from middle`() {
+ val longFileName = "this_is_a_very_long_filename_that_exceeds_forty_characters_to_test_truncation.pdf"
+ val downloadState =
+ DownloadState(
+ fileName = longFileName,
+ url = "mozilla.org/file.pdf",
+ contentLength = 100L,
+ currentBytesCopied = 10,
+ status = DownloadState.Status.DOWNLOADING,
+ )
+ val style = AbstractFetchDownloadService.Style()
+
+ val notification =
+ DownloadNotification.createOngoingDownloadNotification(
+ context = testContext,
+ downloadState = downloadState,
+ fileSizeFormatter = fakeFileSizeFormatter,
+ notificationAccentColor = style.notificationAccentColor,
+ downloadEstimator = DownloadEstimator(dateTimeProvider = FakeDateTimeProvider()),
+ )
+
+ val expectedTitle = "this_is_a_ve…uncation.pdf"
+ assertEquals(expectedTitle, notification.extras.getCharSequence(Notification.EXTRA_TITLE).toString())
+ }
+
@Test
fun getOngoingNotificationAccentColor() {
val download =
diff --git a/mobile/android/android-components/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlin/StringTest.kt b/mobile/android/android-components/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlin/StringTest.kt
index b51602a7068..5c2a57f201d 100644
--- a/mobile/android/android-components/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlin/StringTest.kt
+++ b/mobile/android/android-components/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlin/StringTest.kt
@@ -309,6 +309,16 @@ class StringTest {
}
}
+ @Test
+ fun truncateMiddle() {
+ assertEquals("123…890", "1234567890".truncateMiddle(7))
+ assertEquals("1234567890", "1234567890".truncateMiddle(10))
+ assertEquals("1234567890", "1234567890".truncateMiddle(20))
+ assertEquals("1…0", "1234567890".truncateMiddle(3))
+ assertEquals("1", "1234567890".truncateMiddle(1))
+ assertEquals("123...890", "1234567890".truncateMiddle(9, "..."))
+ }
+
@Test
fun `WHEN a string contains utf 8 encoded characters or illegal filename characters decode decodes it`() {
// List of pairs of encoded strings or illegal filename characters and their expected decoded results
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/mobile/android/android-components/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadNotificationTest.kt b/mobile/android/android-components/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadNotificationTest.kt
index b7f3be776de..8c5fa35727b 100644
--- a/mobile/android/android-components/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadNotificationTest.kt
+++ b/mobile/android/android-components/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadNotificationTest.kt
@@ -4,6 +4,7 @@
package mozilla.components.feature.downloads
+import android.app.Notification
import android.app.PendingIntent
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.EXTRA_PROGRESS
@@ -312,6 +313,32 @@ class DownloadNotificationTest {
assertEquals(true, notificationDownloadWithNoSize.extras.getBoolean(EXTRA_PROGRESS_INDETERMINATE))
}
+ @Test
+ fun `createOngoingDownloadNotification with long filename truncates from middle`() {
+ val longFileName = "this_is_a_very_long_filename_that_exceeds_forty_characters_to_test_truncation.pdf"
+ val downloadState =
+ DownloadState(
+ fileName = longFileName,
+ url = "mozilla.org/file.pdf",
+ contentLength = 100L,
+ currentBytesCopied = 10,
+ status = DownloadState.Status.DOWNLOADING,
+ )
+ val style = AbstractFetchDownloadService.Style()
+
+ val notification =
+ DownloadNotification.createOngoingDownloadNotification(
+ context = testContext,
+ downloadState = downloadState,
+ fileSizeFormatter = fakeFileSizeFormatter,
+ notificationAccentColor = style.notificationAccentColor,
+ downloadEstimator = DownloadEstimator(dateTimeProvider = FakeDateTimeProvider()),
+ )
+
+ val expectedTitle = "this_is_a_ve…uncation.pdf"
+ assertEquals(expectedTitle, notification.extras.getCharSequence(Notification.EXTRA_TITLE).toString())
+ }
+
@Test
fun getOngoingNotificationAccentColor() {
val download =
diff --git a/mobile/android/android-components/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlin/StringTest.kt b/mobile/android/android-components/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlin/StringTest.kt
index b51602a7068..5c2a57f201d 100644
--- a/mobile/android/android-components/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlin/StringTest.kt
+++ b/mobile/android/android-components/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlin/StringTest.kt
@@ -309,6 +309,16 @@ class StringTest {
}
}
+ @Test
+ fun truncateMiddle() {
+ assertEquals("123…890", "1234567890".truncateMiddle(7))
+ assertEquals("1234567890", "1234567890".truncateMiddle(10))
+ assertEquals("1234567890", "1234567890".truncateMiddle(20))
+ assertEquals("1…0", "1234567890".truncateMiddle(3))
+ assertEquals("1", "1234567890".truncateMiddle(1))
+ assertEquals("123...890", "1234567890".truncateMiddle(9, "..."))
+ }
+
@Test
fun `WHEN a string contains utf 8 encoded characters or illegal filename characters decode decodes it`() {
// List of pairs of encoded strings or illegal filename characters and their expected decoded results
Loading diff…
References
On This Page