diff --git a/lib/model/content.dart b/lib/model/content.dart
index 843b9ddda9..52fa173bc2 100644
--- a/lib/model/content.dart
+++ b/lib/model/content.dart
@@ -1413,22 +1413,17 @@ class _ZulipContentParser {
final String srcUrl;
final String? thumbnailUrl;
if (src.startsWith('/user_uploads/thumbnail/')) {
+ // For why we recognize this as the thumbnail form, see discussion:
+ // https://chat.zulip.org/#narrow/channel/412-api-documentation/topic/documenting.20inline.20images/near/2279872
srcUrl = href;
thumbnailUrl = src;
- } else if (src.startsWith('/external_content/')
- || src.startsWith('https://uploads.zulipusercontent.net/')) {
- // This image preview uses camo, which still happens on current servers
- // (2025-10); discussion:
- // https://chat.zulip.org/#narrow/channel/412-api-documentation/topic/documenting.20inline.20images/near/2279235
- srcUrl = src;
- thumbnailUrl = null;
- } else if (href == src) {
- // Probably generated by a server before the thumbnailing feature landed:
- // https://chat.zulip.org/#narrow/channel/412-api-documentation/topic/documenting.20inline.20images/near/2279234
+ } else {
+ // Known cases this handles:
+ // - `src` starts with CAMO_URI, a server variable (e.g. on Zulip Cloud
+ // it's "https://uploads.zulipusercontent.net/" in 2025-10).
+ // - `src` matches `href`, e.g. from pre-thumbnailing servers.
srcUrl = src;
thumbnailUrl = null;
- } else {
- return UnimplementedBlockContentNode(htmlNode: divElement);
}
double? originalWidth, originalHeight;
diff --git a/test/model/content_test.dart b/test/model/content_test.dart
index 59a66f7ce8..bab9b05969 100644
--- a/test/model/content_test.dart
+++ b/test/model/content_test.dart
@@ -785,8 +785,8 @@ class ContentExample {
]),
]);
- static const imagePreviewSingleExternal = ContentExample(
- 'single image preview external',
+ static const imagePreviewSingleExternal1 = ContentExample(
+ 'single image preview external, src starts with /external_content',
// https://chat.zulip.org/#narrow/stream/7-test-here/topic/Greg/near/1892172
"https://upload.wikimedia.org/wikipedia/commons/7/78/Verregende_bloem_van_een_Helenium_%27El_Dorado%27._22-07-2023._%28d.j.b%29.jpg",
'
'
@@ -799,6 +799,36 @@ class ContentExample {
]),
]);
+ static const imagePreviewSingleExternal2 = ContentExample(
+ 'single image preview external, src starts with https://uploads.zulipusercontent.net/',
+ // Zulip Cloud has CAMO_URI = "https://uploads.zulipusercontent.net/";
+ // this example is from a DM on a closed Zulip Cloud org.
+ "https://upload.wikimedia.org/wikipedia/commons/7/78/Verregende_bloem_van_een_Helenium_%27El_Dorado%27._22-07-2023._%28d.j.b%29.jpg",
+ '
', [
+ ImagePreviewNodeList([
+ ImagePreviewNode(srcUrl: 'https://uploads.zulipusercontent.net/99742b0f992be15283c428dd42f3b9f5db138d69/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f372f37382f566572726567656e64655f626c6f656d5f76616e5f65656e5f48656c656e69756d5f253237456c5f446f7261646f2532372e5f32322d30372d323032332e5f253238642e6a2e622532392e6a7067',
+ thumbnailUrl: null, loading: false,
+ originalWidth: null, originalHeight: null),
+ ]),
+ ]);
+
+ static const imagePreviewSingleExternal3 = ContentExample(
+ 'single image preview external, src starts with https://custom.camo-uri.example/',
+ // CAMO_URI (server variable) can be set arbitrarily;
+ // for another possible value, see imagePreviewSingleExternal2.
+ "https://upload.wikimedia.org/wikipedia/commons/7/78/Verregende_bloem_van_een_Helenium_%27El_Dorado%27._22-07-2023._%28d.j.b%29.jpg",
+ '
', [
+ ImagePreviewNodeList([
+ ImagePreviewNode(srcUrl: 'https://custom.camo-uri.example/99742b0f992be15283c428dd42f3b9f5db138d69/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f372f37382f566572726567656e64655f626c6f656d5f76616e5f65656e5f48656c656e69756d5f253237456c5f446f7261646f2532372e5f32322d30372d323032332e5f253238642e6a2e622532392e6a7067',
+ thumbnailUrl: null, loading: false,
+ originalWidth: null, originalHeight: null),
+ ]),
+ ]);
+
static const imagePreviewInvalidUrl = ContentExample(
'single image preview with invalid URL',
null, // hypothetical, to test for a risk of crashing
@@ -1816,7 +1846,9 @@ void main() async {
testParseExample(ContentExample.imagePreviewSingleNoDimensions);
testParseExample(ContentExample.imagePreviewSingleNoThumbnail);
testParseExample(ContentExample.imagePreviewSingleLoadingPlaceholder);
- testParseExample(ContentExample.imagePreviewSingleExternal);
+ testParseExample(ContentExample.imagePreviewSingleExternal1);
+ testParseExample(ContentExample.imagePreviewSingleExternal2);
+ testParseExample(ContentExample.imagePreviewSingleExternal3);
testParseExample(ContentExample.imagePreviewInvalidUrl);
testParseExample(ContentExample.imagePreviewCluster);
testParseExample(ContentExample.imagePreviewClusterNoThumbnails);