ma1 pushed to branch mullvad-browser-153.4.0esr-16.0-1 at The Tor Project / Applications / Mullvad Browser Commits: 4a1b9e9d by giorga at 2026-09-28T10:41:39+02:00 Bug 2054384 - Download notifications should truncate long filenames in the middle. r=android-reviewers,tthibaud Differential Revision: https://phabricator.services.mozilla.com/D321546 - - - - - 4065ff08 by Rob Wu at 2026-09-28T10:41:44+02:00 Bug 2061470 - Don't drop __proto__ from webRequest bodies r=rpl Differential Revision: https://phabricator.services.mozilla.com/D323631 - - - - - 6 changed files: - mobile/android/android-components/components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadNotification.kt - mobile/android/android-components/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadNotificationTest.kt - mobile/android/android-components/components/support/ktx/src/main/java/mozilla/components/support/ktx/kotlin/String.kt - mobile/android/android-components/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlin/StringTest.kt - toolkit/components/extensions/test/mochitest/test_ext_webrequest_upload.html - toolkit/components/extensions/webrequest/WebRequestUpload.sys.mjs Changes: ===================================== mobile/android/android-components/components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadNotification.kt ===================================== @@ -28,6 +28,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 import kotlin.random.Random import kotlin.time.Duration.Companion.seconds @@ -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,10 +108,8 @@ internal object DownloadNotification { ), ) .setSmallIcon(R.drawable.mozac_feature_download_ic_ongoing_download) - .setContentTitle(downloadState.fileName.orEmpty()) - .setContentText( - downloadState.getProgress(fileSizeFormatter = fileSizeFormatter), - ) + .setContentTitle(fileName) + .setContentText(downloadState.getProgress(fileSizeFormatter = fileSizeFormatter)) .setColor(ContextCompat.getColor(context, notificationAccentColor)) .setCategory(NotificationCompat.CATEGORY_PROGRESS) .setProgress(PERCENTAGE_MULTIPLIER, percentCopied, isIndeterminate) @@ -132,10 +133,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), ) @@ -163,10 +165,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( @@ -191,10 +194,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), ) @@ -217,7 +221,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, )}" ===================================== 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 @@ -307,6 +308,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 = DownloadJobState( ===================================== mobile/android/android-components/components/support/ktx/src/main/java/mozilla/components/support/ktx/kotlin/String.kt ===================================== @@ -498,3 +498,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" +} ===================================== mobile/android/android-components/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlin/StringTest.kt ===================================== @@ -306,6 +306,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 ===================================== toolkit/components/extensions/test/mochitest/test_ext_webrequest_upload.html ===================================== @@ -27,6 +27,7 @@ enctype="multipart/form-data"
<input type="text" name="textInput2" value="value2"> +<input type="text" name="__proto__" value="regression test for bug 2061470"> <input type="file" name="testFile"> <input type="file" name="emptyFile"> </form> @@ -161,11 +162,19 @@ add_task(async function test_xhr_forms() { } let action = new URL(form.action); let formData = new FormData(form); - let webRequestFD = {}; let updateActionURL = () => { + let webRequestFD = {}; for (let name of formData.keys()) { - webRequestFD[name] = name in uploads ? [uploads[name].fileName] : formData.getAll(name); + if (Object.hasOwn(webRequestFD, name)) { + // Ignore duplicate keys; formData.getAll already read all values. + continue; + } + const value = Object.hasOwn(uploads, name) ? [uploads[name].fileName] : formData.getAll(name); + // Cannot use webRequestFD[name] = value, because for "__proto__" as + // name, that would trigger the Object.prototype.__proto__ setter + // instead of defining a data property. + Object.defineProperty(webRequestFD, name, { value, enumerable: true }); } action.searchParams.set("upload", JSON.stringify(webRequestFD)); action.searchParams.set("enctype", form.enctype); ===================================== toolkit/components/extensions/webrequest/WebRequestUpload.sys.mjs ===================================== @@ -117,22 +117,6 @@ class Headers extends Map { } } -/** - * Creates a new Object with a corresponding property for every - * key-value pair in the given Map. - * - * @param {Map} map - * The map to convert. - * @returns {object} - */ -function mapToObject(map) { - let result = {}; - for (let [key, value] of map) { - result[key] = value; - } - return result; -} - /** * Rewinds the given seekable input stream to its beginning, and catches * any resulting errors. @@ -446,7 +430,7 @@ function createFormData(stream, channel, lenient) { try { let formData = parseFormData(stream, channel, lenient); if (formData) { - return mapToObject(formData); + return Object.fromEntries(formData); } } catch (e) { Cu.reportError(e); View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/581... -- View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/581... You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
participants (1)
-
ma1 (@ma1)