From 6db5fe8345ed0dce12a52a28529b285f0389c6d5 Mon Sep 17 00:00:00 2001 From: Aleksandar Apostolov Date: Fri, 14 Aug 2026 15:49:33 +0200 Subject: [PATCH] fix: address CDN image resizer review follow-ups Backport of #6640 to v6. - Make custom cdnHost matching additive: isStreamCdnHosted now matches the default STREAM_CDN_HOST_PATTERN OR the custom cdnHost, matching the KDoc - Guard non-positive original dimensions in the max-pixel resize overload (two negatives previously slipped through the positive-product check) - Drop the invalid @Deprecated ReplaceWith on ChatUI.streamCdnImageResizing (it generated uncompilable code); document manual migration instead --- .../client/extensions/StringExtensions.kt | 15 ++++++-------- .../extensions/StringExtensionsKtTest.kt | 20 +++++++++++++++++++ .../io/getstream/chat/android/ui/ChatUI.kt | 4 ++-- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/extensions/StringExtensions.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/extensions/StringExtensions.kt index 3c280aa4489..06f53b873e7 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/extensions/StringExtensions.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/extensions/StringExtensions.kt @@ -43,18 +43,14 @@ public val STREAM_CDN_HOST_PATTERN: Regex = /** * Returns whether this URL is served from a Stream CDN host that understands resizing query parameters. * - * @param cdnHost An optional custom Stream CDN host (e.g. a proxied domain). When provided, the URL host is - * matched against it with a substring check, mirroring iOS' `StreamCDNRequester(cdnHost:)`; when null, the - * default [STREAM_CDN_HOST_PATTERN] is used. + * @param cdnHost An optional custom Stream CDN host (e.g. a proxied domain), matched with a substring check + * in addition to the default [STREAM_CDN_HOST_PATTERN], mirroring iOS' `StreamCDNRequester(cdnHost:)`. */ @InternalStreamChatApi public fun String.isStreamCdnHosted(cdnHost: String? = null): Boolean { val host = this.toUri().host ?: return false - return if (cdnHost != null) { - host.contains(cdnHost, ignoreCase = true) - } else { - STREAM_CDN_HOST_PATTERN.matches(host) - } + return STREAM_CDN_HOST_PATTERN.matches(host) || + (cdnHost != null && host.contains(cdnHost, ignoreCase = true)) } /** @@ -223,8 +219,9 @@ public fun String.createResizedStreamCdnImageUrl( if (maxImagePixels <= 0L) return this if (!isStreamCdnHosted(cdnHost)) return this val dimensions = getStreamCdnHostedImageDimensions() ?: return this + if (dimensions.originalWidth <= 0 || dimensions.originalHeight <= 0) return this val totalPixels = dimensions.originalWidth.toLong() * dimensions.originalHeight.toLong() - if (totalPixels <= 0L || totalPixels <= maxImagePixels) return this + if (totalPixels <= maxImagePixels) return this val scale = sqrt(maxImagePixels.toDouble() / totalPixels.toDouble()).toFloat() return createResizedStreamCdnImageUrl( resizedWidthPercentage = scale, diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/extensions/StringExtensionsKtTest.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/extensions/StringExtensionsKtTest.kt index 84a0a6341dd..d3c44ec0fb8 100644 --- a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/extensions/StringExtensionsKtTest.kt +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/extensions/StringExtensionsKtTest.kt @@ -239,6 +239,26 @@ internal class StringExtensionsKtTest { (resized.toUri().getQueryParameter(QUERY_PARAMETER_KEY_RESIZED_WIDTH) != null) shouldBeEqualTo true } + @Test + fun `custom cdn host matching is additive - default Stream hosts still resize`() { + val streamUrl = createStreamCdnImageLink(originalWidth = 4000, originalHeight = 2000) + + // With a custom host configured, default Stream CDN images must still be resized. + val resized = streamUrl.createResizedStreamCdnImageUrl( + maxImagePixels = 2_000_000L, + cdnHost = "images.example.com", + ) + (resized != streamUrl) shouldBeEqualTo true + (resized.toUri().getQueryParameter(QUERY_PARAMETER_KEY_RESIZED_WIDTH) != null) shouldBeEqualTo true + } + + @Test + fun `Given non-positive original dimensions Should return the url unchanged`() { + val negativeDimsUrl = createStreamCdnImageLink(originalWidth = -4000, originalHeight = -2000) + + negativeDimsUrl.createResizedStreamCdnImageUrl(maxImagePixels = 2_000_000L) shouldBeEqualTo negativeDimsUrl + } + @Test fun `Given a non-positive max pixel budget Should return the url unchanged`() { val originalUrl = createStreamCdnImageLink(originalWidth = 4000, originalHeight = 2000) diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/ChatUI.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/ChatUI.kt index 22bb7aee623..93d7d8be89a 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/ChatUI.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/ChatUI.kt @@ -263,8 +263,8 @@ public object ChatUI { */ @Deprecated( "Use streamCdnImageResizer (defaults to a StreamCdnMaxPixelsImageResizer capping images to 2MP). " + - "For custom behavior, provide your own StreamCdnImageResizer.", - ReplaceWith("streamCdnImageResizer"), + "For custom behavior, provide your own StreamCdnImageResizer. This percentage-based config has no " + + "one-token replacement — migrate manually.", ) @JvmStatic public var streamCdnImageResizing: StreamCdnImageResizing = StreamCdnImageResizing.defaultStreamCdnImageResizing()