From 7fe0cc4d450c753bc237eaf6a1ffccc978cf222e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Mon, 19 Jan 2026 09:55:45 +0100 Subject: [PATCH] Add `MediaSource.withCleanUrl` method that removes invalid fragment data from MXC urls We've seen some MXC urls in the wild having some `mxc://foo/bar#auto` fragment suffix, which is invalid, but the URL before that fragment part is valid and can be displayed --- .../libraries/matrix/api/media/MediaSource.kt | 23 +++++++++++++++++++ .../matrix/impl/media/RustMediaLoader.kt | 3 ++- .../matrix/ui/media/CoilMediaFetcher.kt | 17 +++++++++----- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaSource.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaSource.kt index 56e32ba2fc..9d0ea7fd13 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaSource.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaSource.kt @@ -9,6 +9,7 @@ package io.element.android.libraries.matrix.api.media import android.os.Parcelable +import androidx.core.net.toUri import kotlinx.parcelize.Parcelize @Parcelize @@ -22,3 +23,25 @@ data class MediaSource( */ val json: String? = null, ) : Parcelable + +/** + * Returns a new [MediaSource] with a valid URL. + */ +fun MediaSource.withCleanUrl(): MediaSource { + val uri = this.url.toUri() + if (uri.scheme != "mxc") return this + + // We've seen some MXC urls in the wild having some `mxc://foo/bar#auto` fragment suffix, which is invalid + val cleanedUrl = buildString { + append(uri.scheme) + if (!this.endsWith("://")) { + append("://") + } + append(uri.host) + if (uri.path != null) { + append(uri.path) + } + } + + return this.copy(url = cleanedUrl) +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt index 892c99f64a..653b4d055d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt @@ -14,6 +14,7 @@ import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.api.media.MediaFile import io.element.android.libraries.matrix.api.media.MediaSource +import io.element.android.libraries.matrix.api.media.withCleanUrl import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.Client import org.matrix.rustcomponents.sdk.use @@ -91,7 +92,7 @@ class RustMediaLoader( return if (json != null) { RustMediaSource.fromJson(json) } else { - RustMediaSource.fromUrl(url) + RustMediaSource.fromUrl(withCleanUrl().url) } } } diff --git a/libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/CoilMediaFetcher.kt b/libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/CoilMediaFetcher.kt index 06321f64a9..e08746f8df 100644 --- a/libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/CoilMediaFetcher.kt +++ b/libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/CoilMediaFetcher.kt @@ -16,6 +16,7 @@ import coil3.fetch.SourceFetchResult import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.media.toFile +import io.element.android.libraries.matrix.api.media.withCleanUrl import okio.Buffer import okio.FileSystem import okio.Path.Companion.toOkioPath @@ -28,14 +29,18 @@ internal class CoilMediaFetcher( ) : Fetcher { override suspend fun fetch(): FetchResult? { val source = mediaData.source - if (source == null) { - Timber.e("MediaData source is null") - return null + val mediaSource = when { + source == null -> { + Timber.e("MediaData source is null") + return null + } + source.url.startsWith("mxc:") -> source.withCleanUrl() + else -> source } return when (val kind = mediaData.kind) { - is MediaRequestData.Kind.Content -> fetchContent(source) - is MediaRequestData.Kind.Thumbnail -> fetchThumbnail(source, kind) - is MediaRequestData.Kind.File -> fetchFile(source, kind) + is MediaRequestData.Kind.Content -> fetchContent(mediaSource) + is MediaRequestData.Kind.Thumbnail -> fetchThumbnail(mediaSource, kind) + is MediaRequestData.Kind.File -> fetchFile(mediaSource, kind) } }